How to broadcast a value over functions instead of a function over values?

This is a somewhat unusual usage, but I’m wondering if given

julia> M = [sin cos; cos sin]
2×2 Matrix{Function}:
 sin  cos
 cos  sin

is there an easy way to broadcast the function evaluation over the elements for one specific argument? I want the result

julia> [x(pi/3) for x in M]
2×2 Matrix{Float64}:
 0.866025  0.5
 0.5       0.866025

except using the broadcasting syntax instead of a comprehension. I thought that it’ll be something like

julia> getindex.(Ref(M), CartesianIndices(M)).(pi/3)
ERROR: MethodError: objects of type Matrix{Function} are not callable
Use square brackets [] for indexing an Array.

but this doesn’t work.

Why not treat both M and pi/3 like arguments for a separate method to broadcast?

julia> apply(f, x...) = f(x...)
apply (generic function with 1 method)

julia> apply.([sin cos; cos sin], pi/3)
2×2 Matrix{Float64}:
 0.866025  0.5
 0.5       0.866025
2 Likes

Marking this as the solution, as the following works and is quite concise:

julia> M = [sin cos; cos sin]
2×2 Matrix{Function}:
 sin  cos
 cos  sin

julia> map.(M, pi/3)
2×2 Matrix{Float64}:
 0.866025  0.5
 0.5       0.866025
1 Like

Oh I never realized map returns a Number if given a Number, good to know! Bear in mind this doesn’t work for scalars in general, map falls back to expecting an iterable:

julia> map(sin, pi/3)
0.8660254037844386

julia> struct A end

julia> f(::A) = 1
f (generic function with 1 method)

julia> map(f, A())
ERROR: MethodError: no method matching length(::A)

Presumably, iteration needs to be defined for the type for map to work correctly. The apply solution works as expected:

julia> M = [f f; f f];

julia> apply.(M, Ref(A()))
2×2 Matrix{Int64}:
 1  1
 1  1

There is actually a map method specifically for Numbers to get around iteration so it doesn’t need to allocate an output vector.

julia> struct Num2{T<:Number} t::T end # iterable contains 1 Number
julia> Base.iterate(x::Num2) = iterate(x.t)
julia> Base.iterate(x::Num2, y) = iterate(x.t, y)
julia> Base.length(x::Num2) = length(x.t)

julia> map(sin, Num2(pi/3))
1-element Vector{Float64}:
 0.8660254037844386

julia> @which map(sin, Num2(pi/3))
map(f, A) in Base at abstractarray.jl:2896

julia> map(sin, pi/3)
0.8660254037844386

julia> @which map(sin, pi/3)
map(f, x::Number, ys::Number...) in Base at number.jl:272
julia> π/3 .|> M
2×2 Matrix{Float64}:
 0.866025  0.5
 0.5       0.866025
5 Likes