Suppose I do the following (extracted from actual code):
julia> struct MyTest{F}
f::F
end
julia> function initialize(x::T) where {T}
return MyTest(i->(i==0) ? x : zero(x))
end
initialize (generic function with 1 method)
julia> x = initialize(1.0)
MyTest{getfield(Main, Symbol("##5#6")){Float64}}(getfield(Main, Symbol("##5#6")){Float64}(1.0))
julia> y = initialize(2.0)
MyTest{getfield(Main, Symbol("##5#6")){Float64}}(getfield(Main, Symbol("##5#6")){Float64}(2.0))
julia> typeof(x) == typeof(y)
true
I was not expecting the anonymous functions to be the same and hence the types to be the same. I in fact need these two types to be different. How can I achieve this?
julia> struct MyTest{F}
f::F
end
julia> function initialize(::Val{x}) where {x}
return MyTest(i->(i==0) ? x : zero(x))
end
initialize (generic function with 1 method)
julia> x = initialize(Val(1.0))
MyTest{getfield(Main, Symbol("##5#6")){1.0}}(getfield(Main, Symbol("##5#6")){1.0}())
julia> y = initialize(Val(2.0))
MyTest{getfield(Main, Symbol("##5#6")){2.0}}(getfield(Main, Symbol("##5#6")){2.0}())
julia> typeof(x) == typeof(y)
false
but I’d be also interested to hear why OPs only constructs one function.
function initialize2(::Val{x}) where {x}
@eval foo=i->(i==0) ? $x : zero($x)
return MyTest(foo)
end
I suppose the anonymous function gets parsed once in initialize and any subsequent calls to initialize just recall the same defined symbol… This should solve it by explicitly redefining a new function to be passed to the constructor.
Here’s the deal: it’s each location where you write an anonymous function that a new type gets created. The anonymous function is created by syntax lowering. (If you’re really curious, you can see how the anonymous function works with Meta.@lower function initiialize ..., but it’s messy). The struct that describes the anonymous function is “lifted” out of initialize and created at the same time that the initialize function is created.
The anonymous function that initialize returns is a closure around the argument you pass. In fact, you can even pull out the captured variable with normal field access:
julia> t = initialize(2)
MyTest{getfield(Main, Symbol("##5#6")){Int64}}(getfield(Main, Symbol("##5#6")){Int64}(2))
julia> t.f.x
2
julia> t = initialize(1//2)
MyTest{getfield(Main, Symbol("##5#6")){Rational{Int64}}}(getfield(Main, Symbol("##5#6")){Rational{Int64}}(1//2))
julia> t.f.x
1//2
Also note how the anonymous function is parameterized by the type of the captured variable — that’s so it can be fast and type-stable. Even in the parametric case that @mauro3 describes above, you’re just creating one type for the anonymous function — it’s just that the exact value is known at compile time and so it’s used as the parameter itself.
I guess the big question is why do you need these to compare different? They’ll behave identically. Of course if MyTest is mutable it will create a new object each time. You could also throw a simple mutable anonymous function wrapper in between MyTest and the function to achieve this:
julia> struct MyTest{F}
f::F
end
julia> mutable struct F{FF}
f::FF
end
(f::F)(x...) = f.f(x...)
julia> function initialize(x)
return MyTest(F(i->(i==0) ? x : zero(x)))
end
initialize (generic function with 1 method)
julia> initialize(1) == initialize(1)
false
The types are still the same, but now equality is different.