Struct with block of missing fields

I’d like to have a struct like this.

@with_kw struct my_struct
    x; y; z

    a
    b=fb(a)
    c=fc(b)
    d=fd(c)
    e=fe(d)

    f=ff(e)
    g=fg(f)
    h=fh(g)
    i=fi(h)
end

That can be created in two ways

   my_struct(;x,y,z, a)         
   my_struct(;x,y,z, f)

In the second case, fields a-e would all be missing.

Is this possible ? To allow a block of fields to be missing ?

The altenative is to use two structs. It seems nice to have it all in one though, even if half the fields are missing for many instances.

Why not just write two constructors?

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.

Perhaps also useful: LazyInitializedFields.jl

1 Like

Thanks for the library. I’ll check it out.
you may be right re overall design, there’s just something appealing about a single structure.

Your code is running in which version? I get:

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.

1 Like

That wouldn’t work properly as Julia does not dispatch on keyword arguments (see discussion How can kwargs be used in multiple dispatch - #2 by lmiq).

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.

Thanks. I guess multiple structs is the answer here

I missed that dispatch issue (only checked one of the constructors) and should have proposed a single constructor along the lines of

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.

2 Likes

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.