julia> T = [x->x*k for k in 1:3]
3-element Vector{var"#54#56"{Int64}}:
#54 (generic function with 1 method)
#54 (generic function with 1 method)
#54 (generic function with 1 method)
The three functions in the array are different, but they share the same type. Is this intended? I got the impression that each distinct anonymous function should have its own different type.
Depending on the type of x the functions could even return different types, so that seems strange to me.
Is there a way to avoid it and generate different function types instead? Use case: I would like to use function types to dispatch a hand-written derivative(f) function, for instance
Functions are just callable structs that subtype Function, and the captured value is a field of the struct.
julia> T = [x->x*k for k=1:3];
julia> dump(first(T))
#34 (function of type var"#34#36"{Int64})
k: Int64 1
julia> [T[i].k for i=1:3]
3-element Vector{Int64}:
1
2
3
Conveniently, because they share the same type, they can reside in the same array while maintaining type-stability.
This is not a problem; when a specialization on a different argument type is compiled for one, it is compiled for all.
For with Julia all things are possible.
julia> macro foo(n::Int) :( ($((:(x->x*$k) for k=1:n)...),) ) end
@foo (macro with 1 method)
julia> const fs = @foo(3)
(var"#5#8"(), var"#6#9"(), var"#7#10"())
julia> map(fs) do f; f(2) end
(2, 4, 6)
However, looking at what you’re doing, this will be better served by leaving them all the same type and just accessing their member field:
derivative(f::typeof(T[1])) = f.k;
That said, this isn’t public API (who knows, maybe one day we’ll hash anonymized function bodies so anonymous functions don’t need to be recompiled); you’re better served by going the direct route and making your own callable struct:
struct Foo{K} <: Function; k::K end
(f::Foo)(x) = x*f.k
derivative(f::Foo) = f.k
T = [Foo(k) for k=1:3]
Indeed, but I figure if we’re replacing a lambda (which subtypes Function anyway) it’s probable that we’d want to opt-in to some of those e.g. broadcasting behaviors.
One additional question then: what can I assume on the type of functions? For instance, can I assume that anonymous functions generated in different lines of code with different -> expressions will have different types?
Yes, this is correct for now. I don’t know if it will remain correct forever though, as there’s an implicit assumption with anonymous functions that you don’t care about their name.
If you want greater assurance, you can use gensym with named function syntax:
julia> macro foo(n::Int) :( ($((esc(:($(gensym(:foo))(x) = x*$k;)) for k=1:n)...),) ) end
@foo (macro with 1 method)
julia> @foo(3)
(var"##foo#304", var"##foo#305", var"##foo#306")