As you can see, the contact object is dynamic, in the sense we can set fields in the object, without declaring previously the structure of the object. I understand in Julia we can have a parameter/variable without declared type, as in:
function setProp1(obj) obj.prop1=1 end
But, as i understand it, the obj object must be created from a type with a field prop1. If prop1 is not declared somewhere as a field, setProp1 can’t set dynamically the field prop1. If i want full dynamism, and i want not to worry for performance, there is a way to create “expando objects” in Julia?
Also, I can’t find much information about benchmarking ExpandoObject, but this blog suggests that their performance is similar to a dictionary anyway. That suggests that you won’t lose performance by switching to using a Dict in julia (except to the extent that Julia and C# may implement their dictionaries differently).
Thanks for the detailed answer. Yes, i’m thinking in dot notation. And sorry for my English, i’m thinking in Dict semantic using dot notation, where i’m not worried by the performance.
I played with the “expando object” idea and found a tiny way (≈30 lines) to get a Python-like instance experience in Julia: dynamic attributes + dynamic “methods” with obj.f(args...) syntax, by storing everything in an EasyConfig.Config (nested dict) and auto-binding self via getproperty.
using EasyConfig
@kwdef mutable struct DynObj
cfg::Config = Config()
end
_wrap(c::Config) = DynObj(c)
_wrap(x) = x
function Base.getproperty(o::DynObj, name::Symbol)
name === :cfg && return getfield(o, :cfg)
c = getfield(o, :cfg)
if haskey(c, name)
v = c[name]
return if v isa Function
(args...) -> v(o, args...)
else
_wrap(v)
end
else # create intermediate nodes to allow o.a.b.c = 1
child = Config()
c[name] = child
return DynObj(child)
end
end
function Base.setproperty!(o::DynObj, name::Symbol, v)
name === :cfg && throw(ArgumentError("field :cfg is reserved"))
getfield(o, :cfg)[name] = v isa DynObj ? v.cfg : v
return v
end
Base.propertynames(o::DynObj; private=false) = private ? (:cfg, keys(o.cfg)...) : (keys(o.cfg)...,)
Base.delete!(o::DynObj, k::Symbol) = (delete!(o.cfg, k); o)
Quick demo:
o = DynObj()
o.x = 1
o.msg = "hello"
o.add = (self, y) -> self.x + y
o.greet = self -> "greet: $(self.msg)"
o.add(5) # 6
o.greet() # "greet: hello"
o.sub.name = "child"
o.sub.say = self -> "I am $(self.name)"
o.sub.say() # "I am child"
Caveats:
performance is not the goal here: this is fully dynamic (Dict + Any + closure binding), so it’s mostly for experimentation / prototyping.
reading a missing property creates an empty node (nice for o.a.b.c = ..., but can hide typos).
“methods” here are just function values stored in a dict; the auto-binding returns a closure, so it bypasses Julia’s normal multiple dispatch / method tables and may allocate.