Broadcast for custom type

This does need to be documented better. In the meantime, consider the following pointers that may help:

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 broadcast works is: it looks at the argument types it gets and tries to determine which broadcast_c method handles broadcasting of those types.
  • first it calls _containertype on the types of all arguments
  • then it calls promote_containertype on 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 of broadcast except an additional argument is added after the function argument, which will be a ::Type{...} of your choosing.
  • Then, add methods to _containertype and promote_containertype so that the generic broadcast can pick the correct method.
  • The same applies for broadcast! and broadcast_c!, except that when the LHS is not an AbstractArray, additional methods may be required.
16 Likes