-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: @generated struct
s
#49187
Comments
As another idea, C++ has partial template specialization, so you can define structs differently (different fields, methods, etc) based on template paramters. E.g., you can have a generic template struct, but then you can make partial (or full) specializations with different fields, etc. Not quite the freedom of the |
#8472 has some good discussion on this too. |
There is also a package for this already (ComputedFieldTypes.jl) |
@vtjnash that computes field types, but not the number of fields or things like mutability (and at the expense of adding additional parameters). That package can't solve any of the use cases I listed above. |
@vtjnash You keep mentioning that package in similar issues to this but it just doesn't solve any problem people actually have. For example, being able to avoid the redundant last parameter (M*N) in a static matrix. |
That is because it is not redundant. It stores the value that the runtime needed for the struct layout computation. |
to my knowledge ComputedFieldTypes also still requires struct definitions to be static. Whereas part of the motivation for this is that you could write |
Number of fields issues is solvable by that because I am not saying this is solved fully, only that it is a duplicate issue. |
FWIW, those constraints are also trivially satisfiable by a NamedTuple
|
|
Couldn't this be memoized in the compiler though rather than needing to actually be present in the type signature? |
As an example of what @ToucheSir means: mutable struct MutableNT{T <: NamedTuple}
mutant::T
end
Base.getproperty(x::MutableNT, s::Symbol) = getfield(getfield(x, :mutant), s)
Base.setproperty!(x::MutableNT, s::Symbol, v) = setfield!(x, :mutant, merge(getfield(x, :mutant), NamedTuple{(s,)}((v,))))
julia> let N = 10
nt = NamedTuple{ntuple(i ->Symbol('a' + i - 1), N)}(ntuple(i -> i == 2 ? rand(Int) : rand(("hi", 1 + im, [1,2], Ref{Any}(1))), N))
mnt = MutableNT(nt)
@btime $mnt.b = 10
end;
2.820 ns (0 allocations: 0 bytes)
julia> let N = 20
nt = NamedTuple{ntuple(i ->Symbol('a' + i - 1), N)}(ntuple(i -> i == 2 ? rand(Int) : rand(("hi", 1 + im, [1,2], Ref{Any}(1))), N))
mnt = MutableNT(nt)
@btime $mnt.b = 10
end;
4.820 ns (0 allocations: 0 bytes)
julia> let N = 100
nt = NamedTuple{ntuple(i ->Symbol('a' + i - 1), N)}(ntuple(i -> i == 2 ? rand(Int) : rand(("hi", 1 + im, [1,2], Ref{Any}(1))), N))
mnt = MutableNT(nt)
@btime $mnt.b = 10
end;
23.032 ns (0 allocations: 0 bytes)
julia> let N = 200
nt = NamedTuple{ntuple(i ->Symbol('a' + i - 1), N)}(ntuple(i -> i == 2 ? rand(Int) : rand(("hi", 1 + im, [1,2], Ref{Any}(1))), N))
mnt = MutableNT(nt)
@btime $mnt.b = 10
end;
44.869 ns (0 allocations: 0 bytes) whereas with a custom mutable struct, we have julia> eval(Expr(:struct, true, :(MNT200{$((Symbol(:T, i) for i in 1:200)...)}), Expr(:block, ((:($(Symbol('a' + i -1)) :: $(Symbol(:T,i))) for i in 1:200 ))...)))
julia> @generated function Base.NamedTuple(mnt::MNT200)
Expr(:call, NamedTuple{fieldnames(MNT200)}, Expr(:tuple, (:(getfield(mnt, $i)) for i ∈ 1:200)...))
end;
julia> @generated function MNT200(nt::NamedTuple)
Expr(:call, MNT200, (:(nt[$i]) for i ∈ 1:200)...)
end;
julia> let N = 200
nt = NamedTuple{ntuple(i ->Symbol('a' + i - 1), N)}(ntuple(i -> i == 2 ? rand(Int) : rand(("hi", 1 + im, [1,2], Ref{Any}(1))), N))
mnt = MNT200(nt)
@btime $mnt.b = 10
end;
2.170 ns (0 allocations: 0 bytes) I think though that Jameson's code is actually scaling better than some of the other approaches we tried (maybe this is good enough, what do you think Brian?) but it's still worse than a bespoke mutable struct. |
Good question. I imagine most structs are pretty small in practice, but I know e.g. SciML has some massive ones. For my case this should be good enough, but I imagine something like StaticArrays may have more stringent performance requirements. |
It's doable in C++ though. Maybe that is just a more powerful language though. ;) |
I managed to mostly worked around this, by having the extra parameters in the struct, having a ( |
Warning: this is pretty speculative and surely quite hard to implement and we should have an issue for it.
I think it would be very useful if we had a (sparingly-used) way of generating a struct layout based on its type parameters. This would be analogous to the way we can currently generate a function body based on its type signature. I looked around and couldn't find a pre-existing issue, but this also isn't a very searchable topic.
Here are a few example things this could be used for:
Simpler
SArray
, andMArray
which supports non-isbits typesClick me
Implementing these would be pretty much trivial if you had a
@generated struct
. Something likeThis would make it so that if someone wrote say
that would represent something like
And unlike the current implementation of
MArray
, this would supportsetfield!
on the actual fields we care about letting us easily implementsetindex!
.Even more powerfully,
SArray
could use this to decide that if say you gave itSArray{1000000000, Float64, 1, false}
, that is way way too big to profitably store as an inline struct and instead heap allocate a vector or something to use as its storage.Having a general way to melt, mutate, and then freeze an immutable struct
Click me
so that one could e.g. write
and get a struct equivalent to
People can then freely do things like e.g.
Note that this way we do not bypass the inner constructor of
Rational
.Packages like Accessors.jl would not become unnecessary, but instead would have their scope reduced to intelligently
dealing with the properties and constructors of a type, and this would become an additional tool in their toolkit.
Forbidding specific values from a type signature
Click me
This is maybe too frivilous a use for what would likely be quite heavy machinery, but currently we have no way to put restrictions on the values in a type signature, and we have to rely on inner constructors to reject them. That is, I can write things like
and this is a perfectly valid type, it just will be rejected by all of its inner constructors. Having
@generated struct
s though could allow one to forbid people from even representing an invalid value in a type signature just like how we currently can reject invalid types in a signature:Compactified structs
Click me
Take for example Unityper.jl which takes in an expression like
and then compactifies these structs into one concrete struct with a minimal memory layout:
This works somewhat well, but cannot work currently if we wanted
Foo
to be a parametric type with parametric fields. With a@generated struct
, we could generate a compactified layout precisely tailored to a set of parameters.Just like
@generated
functions, this would be pretty heavy duty stuff that regular users shouldn't be doing, but I think it'dallow authors of serious packages to do a lot of things that currently aren't possible (and in some cases, stop them from doing some worrying pointer shenanigans that doesn't generalize well), so I think having this feature should be an eventual goal.
The text was updated successfully, but these errors were encountered: