In the REPL:
julia> hetero = Function[(x) -> x^2, (x) -> x^3]
2-element Vector{Function}:
#3 (generic function with 1 method)
#5 (generic function with 1 method)
julia> homo = Function[(x) -> x^i for i = 2:3]
2-element Vector{Function}:
#8 (generic function with 1 method)
#8 (generic function with 1 method)
julia> allequal(typeof, hetero)
false
julia> allequal(typeof, homo)
true
From the above we see that your first example vector is a heterogeneous collection, while your second example vector is a homogeneous collection (which just means that all elements have the same type).
What gives? The “vector of functions” part of the question is really just a red herring, this is really about closures and the ways of how types (in the type system sense) of functions can work in Julia.
A function that’s not a closure, that is, a function that doesn’t capture anything, is a value of singleton type. In other words, it’s the only instance of its type. Example:
julia> function f end
f (generic function with 0 methods)
julia> typeof(f)
typeof(f) (singleton type of function f, subtype of Function)
julia> typeof(f).instance # this is an implementation detail, the point is to show that there's just one instance to choose from
f (generic function with 0 methods)
At this point it might be worth it to say two tangentially relevant details:
- A function may have any number of methods, notice the above function
fdoesn’t have any, because adding a method was not relevant for the example. Adding methods toffrom the above example will not (and can not) change the type off. - “Functions” are not very special in Julia. Any type can be made callable by adding methods to it. A special case, for example, are type constructors: a “constructor” is really just a type object with methods attached: Constructors · The Julia Language
However, the same syntax is used for all of these Julia features:
- Declare a new function of singleton type:
function f end - Declare a new method of a function of a singleton type (with the function possibly also being new):
function f() 7 endor() -> 7 - Declare a new method of a closure function, that is, a method that captures some variables from its enclosing scope. For example:
In this case, although the type of the function may be parameterized by the type of the captured value (julia> function f(x) function g() x + 1 end end f (generic function with 1 method) julia> f(1) (::var"#g#f##0"{Int64}) (generic function with 1 method) julia> typeof(ans) var"#g#f##0"{Int64} julia> f(1) isa typeof(f(2)) true julia> typeof(f(1)) == typeof(f(2)) trueInt), the type of the function does not depend on the captured value itself (1vs2).
To sum things up, in your first example you create two function types, while in the other there’s just one type, and it’s a closure.