Can't push a function into a function array

question
array
#1

I am new to Julia. I am trying to initialize an array of functions, and add a function to it.
This is my code:

arr = Array{Function, 1}
push!(arr, sin)

And this is the error I’m getting

MethodError: no method matching push!(::Type{Array{Function,1}}, ::typeof(sin))
Closest candidates are:
  push!(::Any, ::Any, !Matched::Any) at abstractarray.jl:2095
  push!(::Any, ::Any, !Matched::Any, !Matched::Any...) at abstractarray.jl:2096
  push!(!Matched::Array{Any,1}, ::Any) at array.jl:860
  ...

What is my problem?

I also tried this version according to the documentation

arr2 = Array{Function}(undef, 1)
push!(arr2, sin)

but then arr2 is

2-element Array{Function,1}:
 #undef
    sin

and I cannot access the function. When I execute arr2[1] I get

UndefRefError: access to undefined reference

I do not understand why I get this behavior. I would love to some explanation.

0 Likes

#2

Array{Function,1} is a data type. You want an instance of that data type, i.e. you want an actual array, i.e.

arr = Array{Function,1}()

Edit: To explain a bit more…

I do not understand why I get this behavior. I would love to some explanation.

arr2 = Array{Function}(undef, 1)

At this step, you have created a 1-element array of Function types with the first element initialized to undef.

push!(arr2, sin)

When you push sin to this array, you get a 2-element array of Function types, with the second element sin and the first is still undef.

and I cannot access the function. When I execute arr2[1] I get

Julia is 1-indexed, so arr2[1] is grabbing the first element of your array which is undef. If you grad arr2[2], it would be sin.

I hope this helps.

5 Likes

#3

In addition to the excellent explanation of @EricForgy: since Function is not a concrete type, there is no speed gain from specifying it — you might as well use Any. Eg

julia> arr = Vector()
0-element Array{Any,1}

julia> push!(arr, sin)
1-element Array{Any,1}:
 sin
1 Like

#4

What does the undef here mean?
I thought that undef here is the way to initialize an array as written in the documentation:
Array{T}(undef, dims) . So I do not understand why arr2 = Array{Function}(undef, 1) does not give an initialized array of function with dimension 1.

0 Likes

#5

Using a more tightly type can prevent bugs if all you intended to put into the vector was Function. In addition, the comment

is not always true. Julia can use partial type information (e.g. https://julialang.org/blog/2018/08/union-splitting).

1 Like

#6

I think you misunderstood something, undef explicitly means that elements are not initialized and can have arbitrary (for concrete bits types) or undefined values.

0 Likes

#7

Generally, this is may be a good strategy, but not with Function, since it does not cover all things that can act like functions for practical purposes, eg

julia> struct Foo end

julia> (::Foo)(x) = x + 1

julia> g = Foo()
Foo()

julia> g(1)
2

julia> Foo <: Function
false
1 Like

#8

Hi @roi.holtzman :wave:

If you are trying to optimize for speed, it is good to stay away from pushing elements to a vector because that will lead to resizing and allocations etc. So it is better to start with an initialized array and then assign values to it.

So if you want a 1-element array of Function types with the first element equal to sin, there are several ways to do it:

If you are not sure how many elements there will be, you can start with an array constructor with array argument:

arr = Vector{Function}([sin])
1-element Array{Function,1}:
 sin

and then you can push! more functions to this array.

If you know how many elements there will be in your array, e.g. 10, it is probably better to initialize the array first

arr = Array{Function}(undef,10)

and then assign them one by one, e.g.

arr[1] = sin

Depending on where your functions are coming from, you can use iterators:

julia> ftup = (sin,cos,tan)
(sin, cos, tan)

julia> [f for f in ftup]
3-element Array{Function,1}:
 sin
 cos
 tan

Getting back to your example, you had

arr = Array{Function}(undef,1)

which creates a 1-element array whose element is undefined and then you can set it to sin via

arr[1] = sin

I hope that helps.

2 Likes

#9

Even with Function if you know that you will not push callable objects into it.

0 Likes

#10

You are of course right; I just don’t think that this is generally good style in practical Julia code, as allowing ::Function values but not other callables is rarely a meaningful restriction.

I agree with your point in general, for most other abstract types, except for Function.

Also, I think that introducing Function to the language was perhaps a mistake, as it is generally misunderstood. Eg even Base and the standard libraries are full of definitions where Callable, or no restriction, would make much more sense.

0 Likes

#11

Since you want to push! to the vector, you should start with it emptyArray{Function}(undef, 1) is not empty, it has a single, undefined element. If you want it empty, it should be Array{Function}(undef, 0).

To make an empty vector with elements of type T, you can write

arr = Vector{T}() 

But, in fact, there is a nicer and simpler syntax that I prefer: T[]. In your example that would become

arr = Function[]

In summary, you can write

Array{Function}(undef, 0)
Array{Function, 1}(undef, 0)
Array{Function, 1}()
Vector{Function}(undef, 0)
Vector{Function}()
Function[]

And probably some more. Note that Array{Function}() does not work, because the compiler cannot tell if you want a 0 length vector, or a 0x0 matrix etc.

It’s a bit confusing with this many ways, I guess, but it all makes sense when you realize that Vector{T} is just an alias for Array{T, 1}, and that calling the constructors with () is equivalent to calling it with (undef, 0) if the constructor is Array{T, 1}, or (undef, 0, 0) if the constructor is Array{T, 2}, etc. If the constructor is just Array{T}, it becomes ambiguous, so be careful about using that.

In general, I prefer using Vector over Array because it’s clearer and less error prone (you often forget the , 1) . But in this concrete case Function[] wins for me.

5 Likes

#12

If you already have an initial element to start with, like sin, you can write:

arr = Function[sin]

If you just write arr = [sin], then arr will be restricted to containing only elements of type typeof(sin).

This is, BTW, faster than this suggestion

because here you first allocate an array Vector{typeof(sin)} and then convert it to a Vector{Function}.

2 Likes