julia> mutable struct Foo; a; fa; b; fb end
julia> function make_foo(; a)
Foo(a, 2*a, missing, missing)
end
make_foo (generic function with 1 method)
julia> function make_foo(; b)
Foo(missing, missing, b, 2*b)
end
julia> make_foo(; a= 2)
Foo(2, 4, missing, missing)
Though I have to confess that something feels off about this design (several fields computed from other fields and then stored; half of the struct uninitialized). I don’t know the context, but perhaps it’s worth reconsidering the overall design.
julia> function make_foo(; a)
Foo(a, 2*a, missing, missing)
end
make_foo (generic function with 1 method)
julia> function make_foo(; b)
Foo(missing, missing, b, 2*b)
end
make_foo (generic function with 1 method)
julia> make_foo(; a= 2)
ERROR: UndefKeywordError: keyword argument b not assigned
I do not understand how your example could work as methods that only change the keyword arguments do not create new methods, but replace the old ones.
In this particular case, it is not too cumbersome to check all variants of missingness of two kwargs, so I personally would go with that.
On the other hand, I agree that, unless my_struct is mutable, a partially-filled case may just be another type. It is even possible to overload getproperty so that .a-.e access would give missing, although I’d prefer accessor functions for that.
function Base.getproperty(x::my_partial_struct, p::Symbol)
if p in (:a, :b, :c, :d, :e)
return missing
else
return getfield(x, p)
end
end
I’m not 100% sure on this but it seems to me that your original request taken at face value, i.e. having a struct which has both a version where my_struct_instance.a exists and one where it doesn’t defeats the purpose of (at least concretely typed) structs, which should signal to the compiler what the memory layout of an object is and therefore allow it to generate optimal code.
It seems to me that if the compiler, when seeing x::MyStruct, can’t even reason about what fields x does or doesn’t have you’ve basically given up on all the performance benefits of Julia.
I do not think you need to throw away all the performance benefits, if these fields can be either missing or a value of a specific type (like Int) then we can just have all of them as Union{Missing,Int} and initialize accordingly in a complex constructor. Then you can use Base.coalesce to facilitate access with a default value (and the other many Base functions made to interact with missing). Of course, nothing can be used instead too, if the purpose is to fail soon instead of later.
Sure, but taking it at face value I literally meant the fields don’t exist rather than are initialized as some instance of another type that signifies they don’t exist.