hello, I have difficulties in defining a closure within a if-else-end block.
I expect closure(1) would give a function that prints 1, while closure(2) would give a function that prints “not 1”.
But the results are very strange as below. I’m using JuliaPro 0.6.3.1
function closure(choice)
if choice == 1
function fun()
println(1)
end
else
function fun()
println("not 1")
end
end
return fun
end
julia> fff = closure(1)
(::fun) (generic function with 1 method)
julia> fff()
not 1
julia> ggg = closure(2)
ERROR: UndefVarError: fun not defined
Stacktrace:
[1] closure(::Int64) at ./none:12
[2] macro expansion at /Applications/JuliaPro-0.6.3.1.app/Contents/Resources/pkgs-0.6.3.1/v0.6/Atom/src/repl.jl:118 [inlined]
[3] anonymous at ./<missing>:?
If you applied the anonymous function suggestion to your original code it works just fine:
julia> function closure(choice)
fun = if choice == 1
function(a::Vector{Float64})
sum(a)
end
else
function(a::Vector{Int})
prod(a)
end
end
return fun
end
closure (generic function with 1 method)
julia> f = closure(1); g = closure(2);
julia> f([1., 2.])
3.0
julia> g([1, 2])
2
I just think that closure is very essential to functional programming. And, defining (mulitplely dispatched) functions according to different conditions is very natural …
If you applied the anonymous function suggestion to your original code it works just fine:
No. This way does not support multiple dispatch …
let me give a better example:
function closure(choice)
# using closure for "static local variables"
staticlocalvar = 0
# ok: multiple dispatch for set()
function set(var::Float64)
staticlocalvar = var
end
function set(var::Vector{Float64})
staticlocalvar = var
end
# NOT ok: function definitions within if-elseif-else-end
if choice == :sum
function f(x::Float64)
return x + staticlocalvar
end
function f(x::Vector{Float64})
return x .+ staticlocalvar
end
elseif choice == :prod
function f(x::Float64)
return x * staticlocalvar
end
function f(x::Vector{Float64})
return dot(x, staticlocalvar)
end
else
error("choice should be :sum or :prod !")
end
return (set, f)
end
ideally, calling closure(:sum) or closure(:prod) would give me a Tuple of functions set() and f(), and each of them has multiple dispatch …
unfortunately, it does not work because defining function inside if-end is “a known failure and intentional”, why???
using anonymous functions does not support multiple dispatch, as each identifier (e.g. fun) is assigned to one and only one anonymous function. Or, any way out such that we can have multiple dispatch on anonymous function?
using struct as suggested by @tomaklutfu … it’s kind of “artificial”, and most importantly, it loses the possibility of “static local variable” provided by closure, which is essential as Julia does not natively support it in other way …
You can have “local variables” as struct fields as closures do the same I think but the thing with conditional named functions is that the name of functions clash as constant variables. It can be more efficient defining this way rather than unpredictable staticlocalvar in the example.
struct FF{Choice, T} <: Function
staticlocalvar::T
end
If you want it to work that way you can use different names within branches and assign f to that function name.
if choice==:sum
function g(args)
end
f=g
else
function h(args)
end
f=h
end
Read the code provided by @fredrikekre carefully, especially this part:
fun = if choice == 1
function(a::Vector{Float64})
sum(a)
end
else
function(a::Vector{Int})
prod(a)
end
end
return fun
This is NOT what you’re doing - here, the result of the if (in this case the function definition!) is saved into fun and returned. What you’re doing is defining functions without returning them to the caller of the closure.
EDIT: Your code, fixed:
function closure(choice)
staticlocalvar = 0
function set(var::Float64)
staticlocalvar = var
end
function set(var::Vector{Float64})
staticlocalvar = var
end
# totally fine actually
ret = if choice == :sum
function f(x::Float64)
return x + staticlocalvar
end
function f(x::Vector{Float64})
return x .+ staticlocalvar
end
f
elseif choice == :prod
function f(x::Float64)
return x * staticlocalvar
end
function f(x::Vector{Float64})
return dot(x, staticlocalvar)
end
f
else
error("choice should be :sum or :prod !")
end
return (set, ret)
end
julia> function closure(choice)
fun = if choice == 1
function tf1(x::Number)
println("hello")
end
function tf1(x::AbstractVector)
println("is it me")
end
tf1
else
function tf2(x::Number)
println("you are looking for")
end
tf2
end
return fun
end
closure (generic function with 1 method)
julia> f = closure(1)
(::getfield(Main, Symbol("#tf1#25"))) (generic function with 2 methods)
julia> f(1)
hello
julia> f([1,2])
is it me
julia> f = closure(2)
(::getfield(Main, Symbol("#tf2#26"))) (generic function with 1 method)
julia> f(1)
you are looking for
julia> (setfun, sumfun) = closure(:sum)
(set, f)
julia> setfun(1.1)
1.1
julia> sumfun(2.2)
2.4200000000000004
julia> (setfun, prodfun) = closure(:prod)
ERROR: UndefVarError: f not defined
Stacktrace:
[1] closure(::Symbol) at ./none:31
[2] macro expansion at /Applications/JuliaPro-0.6.3.1.app/Contents/Resources/pkgs-0.6.3.1/v0.6/Atom/src/repl.jl:118 [inlined]
[3] anonymous at ./<missing>:?
finally, as @Evizero suggested, the trick is to use different function names inside if-else-end block. Fixed like this:
function closure(choice)
staticlocalvar = 0
function set(var::Float64)
staticlocalvar = var
end
function set(var::Vector{Float64})
staticlocalvar = var
end
# totally fine actually
ret = if choice == :sum
function f(x::Float64)
return x + staticlocalvar
end
function f(x::Vector{Float64})
return x .+ staticlocalvar
end
f
elseif choice == :prod
function g(x::Float64) # !!! g, not f !!! #
return x * staticlocalvar
end
function g(x::Vector{Float64}) # !!! g, not f !!! #
return dot(x, staticlocalvar)
end
g # !!! g, not f !!! #
else
error("choice should be :sum or :prod !")
end
return (set, ret)
end
julia> (setfun, sumfun) = closure(:sum)
(set, f)
julia> setfun(1.1)
1.1
julia> sumfun(2.2)
3.3000000000000003
julia> (setfun, prodfun) = closure(:prod)
(set, g)
julia> setfun(1.1)
1.1
julia> prodfun(2.2)
2.4200000000000004
Interesting - maybe that’s because you used both right after one another? I only tested them individually.
I guess it’s because you can’t redefine an existing symbol, which is what would happen if you call them twice. What’s even more interesting is that the error message about redefining an existing symbol gets swallowed and only after the return when you try to call it do you get an error about a function not being defined.
I think the issue is that named functions in a given scope block are combined, with no regard to the logic of the code. So if we use let blocks to introduce additional scope blocks, I think it works:
function closure(option)
if option == 1
let
f(x::Int) = 1
f(x::Float64) = 1.0
f
end
else
let
f(x::Int) = 2
f(x::Float64) = 2.0
f
end
end
end
Claims that something should be easy to implement that are unaccompanied by correct, efficient implementations are unfortunately both common and not terribly useful. We would, however, be be very happy to accept a PR implementing this. You may want to keep in mind that Jeff, who designed and implemented the way functions work in Julia today, was not and has not yet been able to figure out a way to address this limitation efficiently and generally. That doesn’t mean it’s not possible, but it does suggest that there may be some difficulties and subtleties that may not be immediately apparent to someone who has only just encountered the problem for the first time.
Claims that something should be easy to implement that are unaccompanied by correct, efficient implementations are unfortunately both common and not terribly useful.