This does need to be documented better. In the meantime, consider the following pointers that may help:
-
@davidanthoff’s implementation of
broadcastfor DataValues.jl: https://github.com/davidanthoff/DataValues.jl/pull/9/files
This is a lightly edited version of a “minimal working example” I posted on Gitter, during a discussion with @ChrisRackauckas :
Depending on your use case you can choose to use as much or as little of the generic broadcast code as is necessary. Generally, you do not specialize broadcast, but rather broadcast_c.
I have a quick demo of what broadcast_c means:
julia> struct Poison end
julia> Base.Broadcast._containertype(::Type{<:Poison}) = Poison
julia> Base.Broadcast.promote_containertype(::Type{Poison}, _) = Poison
julia> Base.Broadcast.promote_containertype(_, ::Type{Poison}) = Poison
julia> Base.Broadcast.promote_containertype(::Type{Poison}, ::Type{Array}) = Poison
julia> Base.Broadcast.promote_containertype(::Type{Array}, ::Type{Poison}) = Poison
julia> Base.Broadcast.broadcast_c(f, ::Type{Poison}, _...) = "hijacked broadcasting"
julia> Poison() .+ [1, 2, 3]
"hijacked broadcasting"
- roughly, the way
broadcastworks is: it looks at the argument types it gets and tries to determine whichbroadcast_cmethod handles broadcasting of those types. - first it calls
_containertypeon the types of all arguments - then it calls
promote_containertypeon all of the return values of_containertype
So the part to ensure that Broadcast will dispatch to your desired broadcast_c method is:
struct Poison end
Base.Broadcast._containertype(::Type{<:Poison}) = Poison
Base.Broadcast.promote_containertype(::Type{Poison}, _) = Poison
Base.Broadcast.promote_containertype(_, ::Type{Poison}) = Poison
and the part actually defining the broadcast behavior is:
Base.Broadcast.broadcast_c(f, ::Type{Poison}, _...) = "hijacked broadcasting"
Why do we need to treat arrays separately? In this case, because of ambiguities. The broadcast code in Base itself has the equivalent of
promote_containertype(::Type{Array}, _) = Array
promote_containertype(_, ::Type{Array}) = Array
so there will be ambiguities unless you treat arrays with their own extra methods.
Note broadcast dispatches to broadcast_c and broadcast!(f, ::AbstractArray, ...) dispatches to broadcast_c! in roughly the same way. Note that broadcast! by default is only defined for the LHS an AbstractArray. If you have your own type on the LHS you will need to specialize broadcast! directly instead of broadcast_c! (or perhaps in addition to, if you’d like to call broadcast_c!).
So in summary:
- Do not add methods to
broadcast. - Instead, add methods to
broadcast_c, which has the same signature ofbroadcastexcept an additional argument is added after the function argument, which will be a::Type{...}of your choosing. - Then, add methods to
_containertypeandpromote_containertypeso that the genericbroadcastcan pick the correct method. - The same applies for
broadcast!andbroadcast_c!, except that when the LHS is not anAbstractArray, additional methods may be required.