In Makie I have an abstract type Layoutable. Whenever a Layoutable is created with a Figure passed as the parent, I want the layoutable added to the figure’s children. The problem is that this behavior needs to happen in the constructor, which is unique for each struct. So I have no location where I can place a generic function that allows me to hook behavior into every Layoutable subtype.
Right now I’m doing this with a macro, so one has to create new Layoutable types via macro and the behavior I want is written into the inner constructor. Is there no better way to do this?
Usually the logic would be
function outer(x::Abstract)
# do generic stuff before specific implementation, then
specific_stuff(x)
# do generic stuff after specific implementation
end
The problem is that for construction there is no outer shared by all subtypes, as it’s already given by each struct’s name. People will write Button(...), Legend(...), etc.
hm, this seems indeed hard to do in general without a macro,
since you definitely need to define a method for each type that dispatches to
your outer constructor instead of the normal one.
This is the simplest thing that I could come up with,
but of course that is brittle since it isn’t actually enforced.
struct Inner end
abstract type Layoutable end
struct Parent
children
end
function Layoutable(::Type{T}, parent::Parent, args...; kwargs...) where T <: Layoutable
# generic stuff
child = T(Inner(), parent, args...; kwargs) # specific stuff
push!(parent.children, child)
end
struct Button <: Layoutable
x
Button(::Inner, parent, x; kwargs...) = new(x)
end
Button(p::Parent, args...) = Layoutable(Button, p, args...)
p = Parent([])
b = Button(p, 42)
So the “agreement” would be to always forward the normal constructor to the abstract type first
and via dispatching on a singleton Inner type you could then call the inner constructor.
I’m not sure if this answers your question, but just from the point of view of dispatch you can do things like:
julia> abstract type Layoutable end
julia> struct Axis<:Layoutable
label::String
position::Float64
end
julia> (::Type{T})(figure) where {T<:Layoutable} = T("x", 0.0)
julia> Axis(nothing)
Axis("x", 0.0)
This of course is a silly example, but I imagine something like
function (::Type{T})(figure) where {T<:Layoutable}
# do generic stuff before specific implementation, then
specific_stuff(T, figure)
# do generic stuff after specific implementation
end
And then for every layoutable (e.g. Axis) you just overload specif_stuff(::Type{Axis}, figure).
This worked, my mistake was adding methods for the layoutables like Legend(somearg, otherarg...) directly, which doesn’t work together with a supertype intercept scheme like this. But changing all those methods to layoutable(Type, somearg, otherarg...) etc. and calling that from the abstract constructor works fine.