When creating an immutable struct with a reasonable number of fields and an inner constructor, I find that aligning the arguments to new() with the order of the struct fields to be quite tedious and error prone (especially if adjacent fields have the same type, or I add / reorder types). With a mutable struct, I tend to do the following:
ret = new()
ret.x = x
ret.y = y
ret.z = z
...
return ret
Which is much safer and easier to maintain.
Does anyone have a nice approach to this? It seems like new() doesn’t support kwargs, but it would be cool if it did. I wonder if a macro could be written that would allow new() to take kwargs?
macro construct(T)
dataType = Core.eval(__module__, T)
esc(Expr(:call, T, fieldnames(dataType)...))
end
struct Foo
a
b
c
d
end
function Foo(;
a = nothing,
b = 1,
c = [1,2,3],
d = 3.4
)
@construct(Foo)
end
This is quite creative! And it works fine on the inner constructor by changing T to :new. I think the implicit naming of all the parameters is actually nicer than having to write out all the argument names as kwargs, which likely have the same names as the arguments anyway.
Yes, I was waiting for someone to chime in with this… I find these problems arise even with reasonably sized structs, say with 6 or so fields, especially when they all have the same type.
I wonder if, when calling a similar macro from an inner constructor, there would even be a way to reach up and automatically get the name of the type without explicitly stating it?
Having adjacent fields with the same datatype makes a field misordering error silent. If the fields all have unique types, these kinds of errors are detected by the type system.
I guess part of what I was saying is that maybe you could use outer constructors instead of inner constructors, so then you don’t need new().
If you want default values, then create outer constructor with those values.
When calling a constructor, for most purposes I don’t think you could distinguish between inner and outer constructors?
macro construct(T)
dataType = Core.eval(__module__, T)
esc(Expr(:call, T, fieldnames(dataType)...))
end
struct Foo
a
b
c
d
end
function Foo()
c = [1,2,3]
a = nothing
d = 3.4
b = 1
@construct(Foo)
end