I have a function that updates all fields of a struct in a field-specific way. Here an MWE:
julia> mutable struct Foo
a
b
end
julia> calc_new_value(x, y) = x*y
calc_new_value (generic function with 1 method)
julia> function update!(foo, rules)
for name in fieldnames(Foo)
setfield!(foo, name, calc_new_value(getfield(foo,name), getfield(rules,name)) )
end
end
update! (generic function with 1 method)
julia> foo = Foo(1,2)
Foo(1, 2)
julia> update!(foo, (a=1, b=2))
julia> foo
Foo(1, 4)
where rules may be a struct, a NamedTuple, or something else. I mean “may” not in the sense that the code must be flexible enough to deal with all possibilities but in the sense that I can make the best design choice here.
Right now, update!() is not type-stable.
I think there’s some way I can use @generated to make it type-stable but I can’t figure out how to do that. Any advice?
Foo’s fields are implicitly annotated ::Any so they must be type-unstable. I checked and if you annotate the fields with ::Int, the example becomes type-stable.
Oh that’s interesting, but not exactly what you said. fieldnames ideally works for Foo2 the same way it did for Foo because the field names are the same. Thing is, Foo is a concrete type, an instance of DataType, while Foo2 is a parametric type, an instance of UnionAll. Before computing the field names, unwrap_unionall extracts a DataType from the UnionAll’s body::Any field, hence the uninferrability. I’m not sure if the method or field could be patched without causing problems.
Your fix is a good idea, but use fieldnames(typeof(foo)) to work with fields instead of properties. Those can diverge, for example public properties computed from private fields. Since you’re setfield!ing, it makes more sense to work with fields.