Vector of functions

I am trying to encode a Vector of functions. I wrote the following:

# Does not work if I replace function by AbstractFunction
a = Vector{Function}(undef, 5)
for i in 1:5
    a[i](t) = cos(i*t)
end

I get the error:

 ERROR: syntax: invalid function name "a[i]" around /Users/erlebach/src/2022/rude/giesekus/alex_broken_code_2023-02-17/rude_impl.jl:3
Stacktrace:
 [1] top-level scope
   @ ~/src/2022/rude/giesekus/alex_broken_code_2023-02-17/rude_impl.jl:2

I do not understand why this error occurs. Any insight is appreciated.

It thinks you’re trying to define a function named a[i], which isn’t allowed. Instead you want an anonymous function.

julia> for i in 1:5
           a[i] = t -> cos(i*t)
       end

julia> a[1](τ)
1.0
6 Likes

Before seeing your reply, I queried ChatGPT, and got the following equivalent answer:

ct = [ (t) -> 3*cos(1*t), (t) -> 3*cos(2*t), (t) -> 3*cos(3*t), (t) -> 3*cos(4*t), (t) -> 3*cos(5*t) ]

So I can create a comprehension and update the function with a lambda function.
Thanks for the reply!

1 Like

Note that, because Function is an abstract type, accessing the elements of a Vector{Function} will be type-unstable and cause performance degradation:

julia> d = [t->cos(0t), t->cos(1t), t->cos(2t), t->cos(3t)]
4-element Vector{Function}:
 #3 (generic function with 1 method)
 #4 (generic function with 1 method)
 #5 (generic function with 1 method)
 #6 (generic function with 1 method)

julia> d[1](π/4), d[4](π/4)
(1.0, -0.7071067811865475)

julia> using BenchmarkTools

julia> @btime $d[1](0)
  20.888 ns (1 allocation: 16 bytes)
1.0

Notice that d’s element type is Function, which is an abstract type.:

julia> isabstracttype(Function)
true

this is necessary because each anonymous function inside it is of a different type.

By contrast, a comprehension that generates lambdas will produce a type-stable array:

julia> e = [t->cos(i*t) for i=0:3]
4-element Vector{var"#12#14"{Int64}}:
 #12 (generic function with 1 method)
 #12 (generic function with 1 method)
 #12 (generic function with 1 method)
 #12 (generic function with 1 method)

julia> e[1](π/4), e[4](π/4)
(1.0, -0.7071067811865475)

julia> @btime $e[1](0)
  4.000 ns (0 allocations: 0 bytes)
1.0

Notice that e’s element type is var"#12#14"{Int64}, which is a concrete type:

julia> isabstracttype(var"#12#14"{Int64})
false

Even though all the functions contained in e are of the same type, they perform the desired task. This is because internally they’re just callable structs which capture the value of i.

julia> Tuple(e[j].i for j=eachindex(e))
(0, 1, 2, 3)
6 Likes

Alternatively:

getproperty.(e, :i)
2 Likes

Can I use Zygote on e[2] for example? The answer should be yes. I have not tried it.