Defining broadcast for custom types - the example in the docs fails

I’m using the example from the docs:

struct ArrayAndChar{T,N} <: AbstractArray{T,N}
    data::Array{T,N}
    char::Char
end

Base.size(A::ArrayAndChar) = size(A.data)
Base.getindex(A::ArrayAndChar{T,N}, inds::Vararg{Int,N}) where {T,N} = A.data[inds...]
Base.setindex!(A::ArrayAndChar{T,N}, val, inds::Vararg{Int,N}) where {T,N} = A.data[inds...] = val
Base.show(io::IO, A::ArrayAndChar) = print(io, typeof(A), " ", A.data, " with char '", A.char, "'")

Base.BroadcastStyle(::Type{<:ArrayAndChar}) = Broadcast.ArrayStyle{ArrayAndChar}()

function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ArrayAndChar}}, ::Type{ElType}) where ElType
    # Scan the inputs for the ArrayAndChar:
    A = find_aac(bc)
    # Use the char field of A to create the output
    ArrayAndChar(similar(Array{ElType}, axes(bc)), A.char)
end

"`A = find_aac(As)` returns the first ArrayAndChar among the arguments."
find_aac(bc::Base.Broadcast.Broadcasted) = find_aac(bc.args)
find_aac(args::Tuple) = find_aac(find_aac(args[1]), Base.tail(args))
find_aac(x) = x
find_aac(::Tuple{}) = nothing
find_aac(a::ArrayAndChar, rest) = a
find_aac(::Any, rest) = find_aac(rest)

Broadcasting a custom function seems to work:

a = ArrayAndChar([1, 2, 3, 4], 'x')
func(x) = convert(UInt64, x)
d = func.(a)
println(d)
# outputs:
# ArrayAndChar{UInt64,1} UInt64[0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004] with char 'x'

But if you make func take the type from the outer scope:

a = ArrayAndChar([1, 2, 3, 4], 'x')
tp = UInt64
func(x) = convert(tp, x)
d = func.(a)
println(d)

an error occurs in Base.similar():

ERROR: LoadError: type Nothing has no field char

If you add println(bc) there, it’s easy to see why:

Base.Broadcast.Broadcasted{Base.Broadcast.ArrayStyle{ArrayAndChar}}(func, (Base.Broadcast.Extruded{ArrayAndChar{Int64,1},Tuple{Bool},Tuple{Int64}}(ArrayAndChar{Int64,1} [1, 2, 3, 4] with char 'x', (true,), (1,)),))

ArrayAndChar is encapsulated in Base.Broadcast.Extruded and is not picked up by find_aac().

So, the question is: what’s happening here, and how do I cover all possible cases when defining broadcasting for a custom type? What other functions/methods should be defined? (Also, it feels like generalised versions of find_aac() and Base.similar() should be the default for this broadcast style…)

Seems to be solved by adding a method

find_aac(x::Base.Broadcast.Extruded) = x.x

Not sure why it is needed, or is it enough. Created a PR.

1 Like