-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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: go-like struct composition with splatting syntax? #42777
Comments
I would imagine this could be pretty easily implemented with a macro. Also, if you want to put the fields of |
@jpsamaroo because the point is to get foo2.bar, not foo2.[foo1].bar @vtjnash invalid? |
Duplicate of #4935 |
I wouldn't say this is an exact duplicate. The proposal there is to allow adding fields via explicit subtyping, while the proposal here is syntactic sugar for adding fields in a specific place, with no effect on subtyping. |
That seems almost worse then somehow, since you can't write code against these fields, as they don't participate in dispatch anywhere, and there's no name for them. |
I think you're way over-complicating the issue here @vtjnash; I think this is purely a syntax sugar request; at type-definition time, look up the "splatted type"'s fields, and insert them into the current type definition as if the user had typed them. |
Can we actually discuss this without shutting me down please? |
Here's a more concrete example struct Child
name::String
age::Int
hobbies:Vector{String}
end
struct Student
Child...
currentclass::String
grade::Union{Int, Char}
end
Bobbert = Child("Bobbert Smythe", 9, ["Gaming", "Sewing", "Horseback Riding"])
BobbertStudent = Student("Bobbert Smythe", 9, ["Gaming", "Sewing", "Horseback Riding"], "Math", 76)
# this would make sense with regular splat syntax too
Rowena = Child("Rowena Ravenclaw", 16, ["Magic", "Ravens"])
RowenaStudent = Student(Rowena..., "Charms", 'A') |
@AstroFloof Yes I think this absolutely can be discussed. Thank you for the clarifying example; it always helps to have something more than foo and bar :) |
FYI, the official term in Go is "type embedding." Ref: a short section in Effective Go There seems to be a discussion for removing this for Go 2: golang/go#22013. It does not necessarily mean it's a bad feature for Go and/or Julia, but it's probably worth summarizing their analysis on pros and cons. |
I don't know much Go, but one of my friends and I have this inside joke/dispute of Julia vs. Go, and he showed me this feature of that language. Impressed, I thought this might easily translate to Julia using the above syntax, or something similar. As I understand it, it inherits class/struct attributes like this in python. class Plant
FoodPreferences = []
Food = ""
Size = 0
def grow(self):
if self.Food:
self.Size += 1
class Fuchsia(Plant)
FoodPreferences = ["Fuchsia Food"] # Dammit Jim! I'm a programmer not a botanist.
# Food, Size, and grow() are part of the Fuchsia class
# etc |
If the goal is discussion, I recommend converting this to a Discussion or posting on Discourse, since that is not the general purpose of an issue tracker. In your example, Julia does not inherit methods (which was the point of the issue I linked as duplicate), only the fields. |
My understanding is that the whole point is to "inherit" just the fields, i.e. Student would not be a subtype of Child, just share its fields. So a method |
Yeah that's what I meant. I just provided the closest example in py that wasn't quite the same. I opened this here, because as far as I was aware, this was the place to put feature requests for the language. |
I would find this syntax helpful.
This is what I do now. Except my macro defines the fields to be splatted, rather than extracting them from a concrete
Yes, I think that is the intention in this example.
|
FWIW, this can in principle be achieved with a macro: julia> macro splatembed(expr::Expr)
fields = Any[]
for f in expr.args[3].args
if isa(f, Expr) && f.head === :(...)
T = Base.eval(__module__, f.args[1])
for n in fieldnames(T)
push!(fields, :($(n)::$(fieldtype(T, n))))
end
else
push!(fields, f)
end
end
return Expr(:struct, expr.args[1], expr.args[2], Expr(:block, fields...))
end
@splatembed (macro with 1 method)
julia> struct Child
name::String
age::Int
hobbies::Vector{String}
end
julia> @splatembed struct Student
Child...
currentclass::String
grade::Union{Int, Char}
end
julia> dump(Student)
Student <: Any
name::String
age::Int64
hobbies::Vector{String}
currentclass::String
grade::Union{Char, Int64} |
What happens if |
Are you asking what does my quick-and-dirty proof-of-concept macro do then or what would be the desired outcome? julia> struct Foo{T}
x::T
y::Vector{T}
end
julia> @splatembed struct Bar1
Foo...
end
julia> dump(Bar1)
Bar1 <: Any
x::Any
y::Vector{T} where T
julia> @splatembed struct Bar2
Foo{Int}...
end
julia> dump(Bar2)
Bar2 <: Any
x::Int64
y::Vector{Int64}
julia> @splatembed struct Bar3{T}
Foo{T}...
end
ERROR: LoadError: UndefVarError: T not defined I'd say the result for struct Bar3{T}
x::T
y::Vector{T}
end and that should also be possible, but needs a more sophisticated macro, obviously. |
Yea sorry, I meant what the desired outcome would be in a case like
should the parameters of |
I think we'd have to require the splatted type to be fully instantiated. So you could do
or
but not |
Unless the parent struct has type-dependent attributes I don't think it is necessary... struct Foo{T} where T <: Integer # i don't know if that's valid syntax
stuff::Vector{T}
end
struct Bar{T2}
other::T2
Foo{Int16}...
end |
Another difference to #4935 is that this could be used like a mixin by splatting more than one other type. This would at least require that the types of a fields splatted into from different structs are uniquely defined by their name, i.e. the splatted structs may both contain a |
For future reference, there's a package that does exactly what is being discussed in this thread: CompositeStructs.jl. |
Oh sweet! I'd forgotten about this but that package is great! I'll close this now in favour of just using that then. |
One thing to keep in mind, though, while the package works well for straightforward use, there are corner cases where it breaks (all of those I have encountered so far seem fixable, though). Plus, using it in combination with |
The Go language has this cool feature for struct composition where if you put one struct after another's values, its values are inherited into the other.
This seems like it would fit well into Julia.
My suggested implementation would be:
Thanks for taking the time to read this! It would be yet another pretty nifty trick that Julia could use.
The text was updated successfully, but these errors were encountered: