Search code examples
juliaanonymous-function

In Julia, what is the type of an anonymous function when an outer scope is involved?


I can create an anonymous function within a list comprehension in Julia. I expect that this would create a type of Vector{Function}. Instead, the type is something like Vector{var"#2#4"}.

For example, when involving an outer scope with an anonymous function

typeof(x->x) <: Function  # true

a = 1
typeof(x-> x + a) <: Function  # false

while

f(x) = x
typeof(f) <: Function  # true

a = 1
g(x) = x + a
typeof(g) <: Function  # true

Why is the anonymous function typed differently than the regular function when the outer scope is involved?

Now, in a list comprehension:

typeof([x->x, x->x]) <: AbstractVector{Function}  # true
typeof([x->x+i for i in 1:2]) <: AbstractVector{Function}  # false
typeof([x->x for i in 1:2]) <: AbstractVector{Function}  # false

whether or not the index i is involved. I initially expected true in each case.


Solution

  • First note that your initial example is not correct, as you have:

    julia> typeof(x->x) <: Function  # true
    true
    
    julia>
    
    julia> a = 1
    1
    
    julia> typeof(x-> x + a) <: Function
    true
    

    So both anonymous functions have a type that is a subtype of Function.

    Now as for comprehension note that this is mostly unrelated with the presence of parameter, as you have:

    julia> typeof([x->x, x->x]) <: AbstractVector{Function}
    true
    
    julia> typeof([x->x]) <: AbstractVector{Function}
    false
    
    julia> typeof([x->x for i in 1:2]) <: AbstractVector{Function}
    false
    

    The reason for this behavior is when you write something like [...] then Julia picks a narrow element type of the comprehension (if it did not try to do so you would have Any element type - the behavior that Python have for its standard lists).

    So now how to solve your issue.

    To make sure that comprehension has element type of Function you need to write:

    julia> typeof(Function[x->x]) <: AbstractVector{Function}
    true
    
    julia> typeof(Function[x->x for i in 1:2]) <: AbstractVector{Function}
    true
    

    See also here and here in the manual where it explains how element type for a result of a comprehension and an array literal is picked.

    If you omit the Function prefix the element type is narrower, and in this case the proper subtyping condition would be as follows:

    julia> typeof([x->x]) <: AbstractVector{<:Function}
    true
    
    julia> typeof([x->x for i in 1:2]) <: AbstractVector{<:Function}
    true
    

    (note the extra <:).

    This behavior is related to the fact that in Julia such parametric types are invariant, which is explained here in the Julia manual.