Julia code and struct generation

It’s nice that you have a fixed 2^4=16 types, but that’s still many types to define, dispatch methods over, and compile. That’s not an ideal situation, and it’s worth considering refactoring.

Asking for metaprogramming tips in part to name your types like A_0x0, A_0x1, etc, is usually an indication that the types shouldn’t be so different, in which case the previous advice to use parametric types with the same fields is warranted. You’d still have to compile for each concrete parametric type, but you can maintain just 1 definition. You don’t have to store nothing, you could dispatch methods over the concrete types as you would for separately defined types and just ignore the fields that don’t semantically mean anything; a method for the abstract A type could also skip the fields depending on the X-nibble. Removing select fields in separate struct definitions doesn’t necessarily save memory because of architectural structure alignment.

Depending on what you need to do with the instances, you might not even need separate types at all, like how a complex hierarchy of user concepts in an application can actually be implemented as one type with one underlying behavior. For example, if you do something as simple as iterating over the existing v_ fields and their particular operations, you could just store the X-nibble per instance and use it to skip the dummy fields to the existing fields.

That said, if maintaining a eval-generated set of types is the intuitive and working solution, then hypothetical refactoring and learning to deal with new extraneous details is a tough sell, especially if the types have completely unrelated methods. I don’t do much metaprogramming and it is not trivial to mutate expressions or deal with macro hygiene, but fundamentally you mutate Arrays in Exprs or you interpolate Expr into each other in the quoted form that looks more like source code. Quickly checked that splatting interpolation works for annotated struct fields:

julia> fields = [:(v::Int), :(v1::String)];

julia> :(struct X
           $(fields...)
       end)
:(struct X
      #= REPL[2]:2 =#
      v::Int
      v1::String
  end)

julia> push!(fields, :(v2::Bool));

julia> :(struct X
           $(fields...)
       end)
:(struct X
      #= REPL[4]:2 =#
      v::Int
      v1::String
      v2::Bool
  end)

julia> :(struct X
           $(fields...)
       end) |> eval

julia> fieldnames(X)
(:v, :v1, :v2)

julia> X(1, "a", true)
X(1, "a", true)

Process the type name’s symbol and the fields over the 16 X-nibble values in an eval/@eval loop, and you got your 16 types. If you find yourself defining the methods in such a loop with very little change however, do consider type refactoring.

1 Like