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, and MArray which supports non-isbits types
Click me
Implementing these would be pretty much trivial if you had a @generated struct. Something like
@generated struct SArray{Size, T, N, IsMutable} <: AbstractArray{T, N}
fields = map(1:prod(Size)) do i
name = Symbol(:_, i)
:($name :: $T)
end
Expr(:block, Expr(:mutable, IsMutable), fields...)
end
This would make it so that if someone wrote say
SArray{(2, 2), String, 2, true}
that would represent something like
mutable struct SArray_2_2_String_2_true
_1 :: String
_2 :: String
_3 :: String
_4 :: String
end
And unlike the current implementation of MArray, this would support setfield! on the actual fields we care about letting us easily implement setindex!.
Even more powerfully, SArray could use this to decide that if say you gave it SArray{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
@generated struct Melted{T}
fields = map(1:fieldcount(T)) do i
name = fieldname(T, i)
type = fieldtype(T, i)
:($fieldname :: $fieldtype)
end
constructor = quote
Melted(x::T) where {T} = new{T}($((:(getfield(x, $i) for i in 1:fieldcount(T))...))
end
Expr(:block, Expr(:ismutable, true), fields..., constructor)
end
so that one could e.g. write
and get a struct equivalent to
mutable struct Melted_Complex_Float64
re::Float64
im::Float64
MeltedComplexFloat64(x::ComplexFloat64) = new(getfield(x,1), getfield(x, 2))
end
People can then freely do things like e.g.
let m = Melted(big(1) // big(2))
m.num = big(2)
m.den = big(3)
Rational(m)
end
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 structs 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:
julia> struct Blah{T <: Integer} end
julia> Blah{String}
ERROR: TypeError: in Blah, in T, expected T<:Integer, got Type{String}
Compactified structs
Click me
Take for example Unityper.jl which takes in an expression like
@compactify begin
@abstract struct Foo
common_field::Int = 1
end
struct a <: Foo
a::Bool = true
b::String = "hi"
end
struct b <: Foo
a::Int = 1
b::Complex = 1 + im
end
end;
and then compactifies these structs into one concrete struct with a minimal memory layout:
julia> dump(a())
Foo
common_field: Int64 1
###a###2: Int64 1
###Any###3: String "hi"
###tag###4: var"###Foo###1" ₋₃₋₁₂₉Foo₋__a₋₃₋₁₉₉₂₋₋
julia> dump(b())
Foo
common_field: Int64 1
###a###2: Int64 1
###Any###3: Complex{Int64}
re: Int64 1
im: Int64 1
###tag###4: var"###Foo###1" ₋₃₋₁₂₉Foo₋__b₋₃₋₁₉₉₂₋₋
julia> fieldtypes(Foo)
(Int64, Int64, Any, var"###Foo###1")
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'd
allow 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.
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, andMArraywhich 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
SArray{(2, 2), String, 2, true}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,
SArraycould 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
Melted{Complex{Float64}}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
Array{Float64, -1000.1}and this is a perfectly valid type, it just will be rejected by all of its inner constructors. Having
@generated structs 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
Footo 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
@generatedfunctions, 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.