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…)