# The Broadcasting API and small Unions

#1

After trying some near examples and the docs, I have not yet made the Broadcasting API do this. More explicit guidance is appreciated.

``````abstract type OtherAbstractFloat <: AbstractFloat end
primitive type Float64a <: OtherAbstractFloat 64 end
primitive type Float64b <: OtherAbstractFloat 64 end

const Float64s = Union{Float64, Float64a, Float64b}
const MaybeFloat64s = Union{Missing, Float64, Float64a, Float64b}

# Float64a, Float64b work like Float64; show with '\^a', '\^b'

Base.Float64(x::T) where {T<:OtherAbstractFloat} = reinterpret(Float64, x)
Float64a(x::Float64) = reinterpret(Float64a, x)
Float64b(x::Float64) = reinterpret(Float64b, x)

Base.show(io::IO, x::Float64a) = print(io, string(Float64(x),"ᵃ"))
Base.show(io::IO, x::Float64b) = print(io, string(Float64(x),"ᵇ"))

square(x::T) where {T<:AbstractFloat} = T(Float64(x)^2)
square(x::Missing) = missing

testvec1 = Float64s[Float64(1.0), Float64a(2.0), Float64b(3.0)]
testvec2 = MaybeFloat64s[Float64(1.0), Float64a(2.0), missing]

# this happens without using the Broadcasting API

square.(testvec1)
3-element Array{AbstractFloat,1}:
1.0
4.0ᵃ
9.0ᵇ

square.(testvec2)
3-element Array{Any,1}:
1.0
4.0ᵃ
missing

# I want to obtain

square.(testvec1) # 3-element Array{Float64s,1}
3-element Array{Union{Float64, Float64a, Float64b},1}:
1.0
4.0ᵃ
9.0ᵇ

square.(testvec2) # 3-element Array{MaybeFloat64s,1}
3-element Array{Union{Missing, Float64, Float64a, Float64b},1}:
1.0
4.0ᵃ
missing
``````

#2

Seems that when the number of elements in the union goes over two, inference falls back to Any.

``````julia> Base._return_type(square, Tuple{Union{Missing, Float64}})
Union{Missing, Float64}

julia> Base._return_type(square, Tuple{Union{Missing, Float64, Float64a}})
Any
``````

#3

That seems inconsistent with all the effort done to make working with small unions (up to four types) performant. As I understand it, most of that work is about handling vectors and arrays of elements typed as small unions. I thought that the Broadcasting API could be used to get the result I seek by using it to make this logic happen:

``````
{N, T<:Float64s}
result = similar(x)
@inbounds @simd for idx in eachindex(view(x, axes(x)...,))
result[idx] = fn(x[idx])
end
return result
end
``````

#4

Not taking into a account `fn` when allocating the `result` doesn’t seem like it would work. What if `fn` was `x -> convert(Int, x)`?

A workaround is:

``````res = similar(testvec2);
res .= square.(testvec2)
``````

#5

Yes, that is true. In my application I need to make arrays of small unions of Float64-like primitive types respond to floating point math functions just as arrays of Float64s do. So I know the result types for each group of functions I “delegate”.

That workaround looks very helpful. Is there a way to use it with a specialized Broadcast Style and `broadcast_similar` so that a client app could write `squares = square.(testvec1)` and generally `res = mathfunction.(testvec1)`?

#6

In Julia 0.6:

``````julia> square(x::Int)=x*x
square (generic function with 2 methods)

julia> Base._return_type(square, Tuple{Union{Int64, Float64}})
Union{Float64, Int64}

julia> arr=Union{Int64,Float64}[1,2.0]
2-element Array{Union{Float64, Int64},1}:
1
2.0

julia> square.(arr)
2-element Array{Real,1}:
1
4.0

``````

#7

#8

FWIW, I was wrong here, `_return_types` is only used if the return type is concrete.