[ANN] CompositeStructs.jl

While I generally view multiple-dispatch as much more powerful and flexible that object-oriented programming (see this talk JuliaCon 2019 | The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski - YouTube for IMO a great explanation why), there are certain object-oriented ideas I occasionally would find useful in Julia.

One of them is the ability to “inherit” fields from a “super type”. There’s a couple of packages I know about that do this (Mixers.jl and Classes.jl) but both require the “super type” struct to have been decorated with a special macro, so you’re out of luck if the author didn’t do that.

CompositeStructs.jl lets you “inherit” fields (by explicitly including them in your struct definition) from any number of other structs with no requirement on how these structs were defined. Unlike Classes.jl, it also lets you decide which, if any, abstract type to take as a super type. By inheriting the fields from an existing struct and opting into a well-designed abstract type hierarchy, it seems to me you can go a long way to getting the best of both worlds from multiple-dispatch and traditional OO inheritance.

The syntax is just e.g.:

struct Foo{X,Y}
    x :: X
    y :: Y
end

@composite struct Bar{X,Y,Z}
    Foo{X,Y}...
    z :: Z
end

# equivalent to defining:
struct Bar{X,Y,Z}
    x :: X
    y :: Y
    z :: Z
end

Another useful feature is that its compatible with Base.@kwdef, and the defaults from the “super type” are propagated to your new struct as well (see readme for details).

Package is registered so to install just: pkg> add CompositeStructs. Let me know if you run into issues and happy if anyone finds this useful.

21 Likes

Interesting idea. What is the use case you have in mind where you’d rather do Bar(x,y,z) than keep the interfaces distinct with composition Bar(Foo(x,y),z)?

4 Likes

Looking forward to using this with Base.@kwdef. I have big immutable parameter structs with 10-30 parameters, and I expect this will make it much less verbose when defining a new concrete type with the same abstract parent type.

1 Like

I think its probably a pretty similar use case, just a slightly different way to go about it. Sometimes to me the lines get pretty blurry between “does Bar have a Foo or is Bar a modified kind of Foo” and this package is if you’re leaning the latter. For the former, if you do want Bar to behave like a Foo in some scenarios, you will probably still need something like “delegation” (I found this thread informative) so in some sense this package takes the place of those if you choose to go this route.