0.6: how do you broadcast `call` over a vector of functions?

broadcast

#1

I’m trying to broadcast a function call over a vector of functions, but call.(f,0.1) is deprecated. I tried f.(0.1) but it doesn’t work:

julia> f=[x->x,x->x^2]
2-element Array{Function,1}:
 #5
 #6

julia> f.(0.1)
ERROR: MethodError: objects of type Array{Function,1} are not callable
Use square brackets [] for indexing an Array.
 in broadcast(::Array{Function,1}, ::Float64) at ./broadcast.jl:16

julia> call.(f,0.1)
WARNING: call(f,args...) is deprecated, use f(args...) instead.
 in depwarn(::String, ::Symbol) at ./deprecated.jl:64
 in ##1003#1004(::Array{Any,1}, ::Function, ::Function, ::Float64, ::Vararg{Float64,N}) at ./deprecated.jl:272
 in (::Base.##1003#1005)(::Function, ::Float64, ::Vararg{Float64,N}) at ./deprecated.jl:271
 in broadcast_t(::Function, ::Type{Any}, ::Array{Function,1}, ::Vararg{Any,N}) at ./broadcast.jl:222
 in broadcast(::Function, ::Array{Function,1}, ::Float64) at ./broadcast.jl:230
 in eval(::Module, ::Any) at ./boot.jl:234
 in eval(::Module, ::Any) at /Users/solver/Projects/julia/usr/lib/julia/sys.dylib:?
 in eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:64
 in macro expansion at ./REPL.jl:95 [inlined]
 in (::Base.REPL.##3#4{Base.REPL.REPLBackend})() at ./event.jl:68
while loading no file, in expression starting on line 0
WARNING: call(f,args...) is deprecated, use f(args...) instead.
 in depwarn(::String, ::Symbol) at ./deprecated.jl:64
 in ##1003#1004(::Array{Any,1}, ::Function, ::Function, ::Float64, ::Vararg{Float64,N}) at ./deprecated.jl:272
 in macro expansion at ./broadcast.jl:192 [inlined]
 in _broadcast!(::Base.##1003#1005, ::Array{Float64,1}, ::Tuple{Tuple{Bool},Tuple{}}, ::Tuple{Tuple{Int64},Tuple{}}, ::Tuple{Array{Function,1},Float64}, ::Type{Val{2}}, ::CartesianRange{CartesianIndex{1}}, ::CartesianIndex{1}, ::Int64) at ./broadcast.jl:179
 in broadcast_t(::Function, ::Type{Any}, ::Array{Function,1}, ::Vararg{Any,N}) at ./broadcast.jl:225
 in broadcast(::Function, ::Array{Function,1}, ::Float64) at ./broadcast.jl:230
 in eval(::Module, ::Any) at ./boot.jl:234
 in eval(::Module, ::Any) at /Users/solver/Projects/julia/usr/lib/julia/sys.dylib:?
 in eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:64
 in macro expansion at ./REPL.jl:95 [inlined]
 in (::Base.REPL.##3#4{Base.REPL.REPLBackend})() at ./event.jl:68
while loading no file, in expression starting on line 0
2-element Array{Float64,1}:
 0.1 
 0.01

#2

(x->x(.1)).(f) would do it, but probably not what you are looking for.


#3

That would recompile for every value you evaluate at


#4

Would it?

fs=[x->x,x->x^2];
apply(f, xx) = (x->x(xx)).(f)

Now

<< @time apply(fs, 2.0) # First run, compilation overhead
  0.947333 seconds (1.37 M allocations: 63.188 MiB, 3.33% gc time)
<< @time apply(fs, 3.0) # Second at different value
  0.000115 seconds (53 allocations: 1.875 KiB)

#5

Yes making your own call implementation will solve the problem, though it’s a bit annoying that you can’t access it directly any more.


#6

What do you mean with “access it directly”?


#7

There used to be a function call that corresponded to calling a function, which is now deprecated. So I mean accessing call without having a deprecation warning.

But this is a very minor issue (in fact, I already had such a function but I was hoping for something more canonical)


#8

If speed is a concern and you have a small set of functions, then f being a tuple of functions could be much better. map should work, with a closure.

Otherwise, you would end up with, at the least, a dynamic dispatch each call (two, if you include an anonymous function/closure to wrap it).

For long lists of functions where you need speed too, you’ll need to arrange your own dispatch manually. Vectors populated by cfunction followed by ccall could be one option, or if the functions are a fixed, known set, you could keep an Int and branch to the correct call. These options work best if the return types are all the same - if not you pretty much need to use a tuple of functions.


#9

Speed isn’t that much of an issue since the function calls themselves are fairly expensive, so a dynamic dispatch is fine. Compiling takes forever, however.

It’s also ugly.


#10

Also:

julia> f=[x->x,x->x^2]
2-element Array{Function,1}:
 #1
 #2

julia> @time (x->x(.1)).(f)
  1.129888 seconds (1.69 M allocations: 68.463 MiB, 9.62% gc time)
2-element Array{Float64,1}:
 0.1 
 0.01

julia> @time (x->x(.1)).(f)
  0.042708 seconds (9.63 k allocations: 480.332 KiB)
2-element Array{Float64,1}:
 0.1 
 0.01

julia> @time (x->x(.2)).(f)
  0.029566 seconds (9.63 k allocations: 480.598 KiB)
2-element Array{Float64,1}:
 0.2 
 0.04

#11

Those timings are pretty bad…


#12

As an illustration:

julia> f=(x->x,x->x^2)
(#5,#6)

julia> apply(fs::Tuple, x) = _apply(x, (), fs...)
apply (generic function with 1 method)

julia> _apply(x, out::Tuple) = out
_apply (generic function with 1 method)

julia> @inline _apply(x, out::Tuple, f, fs...) = _apply(x, (out..., f(x)), fs...)
_apply (generic function with 2 methods)

julia> @time apply(f, 0.1)
  0.005076 seconds (2.81 k allocations: 133.484 KB)
(0.1,0.010000000000000002)

julia> @time apply(f, 0.1)
  0.000002 seconds (5 allocations: 192 bytes)
(0.1,0.010000000000000002)

julia> @time apply(f, 0.2)
  0.000002 seconds (5 allocations: 192 bytes)
(0.2,0.04000000000000001)

That’s a 10^4 speed improvement.


Manually unroll operations with objects of tuple
How to make a vector of parametric composite types?
#13

Ah OK, in my actual use case they are Funs, not anonymous functions, so the overhead in compiling the entries is not there, but the overhead in compiling x->x(0.1) is


#14

What’s a Fun?


#15

From ApproxFun, represents a function as coefficients in a basis (so no need to compile for different functions)


#16

Plz stop timing things in global scope…


#17

:blush: sorry . In my case, I didn’t mean for the actual timings to mean anything, just to say I had the impression it wasn’t recompiling for new values of the floating point argument. Point taken, of course.


#18

Couldn’t you define f = x -> [x, x^2]?

f.(0.1) would work with the anonymous function above.

EDIT: An example of the above for Fun (pun intended)

g = Fun(identitiy, -1..1)
h = Fun(x->x^2, -1..1)
f = x -> [g(x), h(x)]
f.(0.1) == [g(0.1), h(0.1)] #true

#19

No: its in a function that is working with a long Vector{Fun}. Fortunately for Funs there is already a function evaluate(f,x) that works fine.

The question was more curiousness than looking for work arounds. I think the answer is “No, you cannot directly broadcast a function call”


#20

This is mostly unreadable but I think it can qualify as directly.

julia> fs = [x->x, x->x^2]
2-element Array{Function,1}:
 #1
 #2

julia> (|>).(2.0, fs)
2-element Array{Float64,1}:
 2.0
 4.0

Personally I would just define my own call function (call deprecation is gone from 0.6) and use that.

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

julia> call.(fs, 2.0)
2-element Array{Float64,1}:
 2.0
 4.0