Defining a function inside if...else..end NOT as expected?

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>:?

It is a known failure and intentional, I think. You should use anonymous function for conditional function generation. It should look like

if ...
 fun = () -> ....
...
else
 fun = () -> .....
end

or

if ....
 fun = function (args)

 end
else
 fun =function (args)
 end
end
2 Likes

sadly …

two problems with this approach:

  1. multiple dispatch not work !!! e.g.

ff = function(a::Vector{Float64})
    sum(a)
end

ff = function(a::Vector{Int64})
    prod(a)
end

julia> ff([1.0, 2.0])
ERROR: MethodError: no method matching (::##135#136)(::Array{Float64,1})
Closest candidates are:
  #135(::Array{Int64,1}) at none:2

julia> ff([1, 2])
2

the first float version is overwritten by the integer version !!! actually it each method should be kept ???

  1. return type can not be specified??? e.g. function like below gives error :tired_face:

ff = function(a::Vector{Float64})::Float64
    sum(a)
end

ERROR: ParseError("expected \"(\" in function definition")
Stacktrace:
 [1] #parse#236(::Bool, ::Bool, ::Function, ::String, ::Int64) at ./parse.jl:222
 [2] (::Base.#kw##parse)(::Array{Any,1}, ::Base.#parse, ::String, ::Int64) at ./<missing>:0
 [3] #parse#237(::Bool, ::Function, ::String) at ./parse.jl:232
 [4] 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]
 [5] anonymous at ./<missing>:?
1 Like

I don’t know what you want to achieve but I have an idea that you can make your own custom Function type like

struct FF{choice} <: Function
end

Now, you can overload (::FF{1})(args), (::FF{2})(args), etc and conditionally assign ff.

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
4 Likes

thanks.

I just think that closure is very essential to functional programming. And, defining (mulitplely dispatched) functions according to different conditions is very natural …

1 Like

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 …

Optimization so that the method table can be constructed in advance.

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

Optimization so that the method table can be constructed in advance.

Sorry …I’m not convinced … as long as a human could “parse” some code without much difficulty, it should be easy for the optimizer to do the job.

I just don’t see why multiply-dispatched-functions can not be defined in a if-end block within a closure …

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

This seems to work:

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
2 Likes

totally fine actually

not really. It compiles, but if you try to run:

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.

16 Likes

Claims that something should be easy to implement that are unaccompanied by correct, efficient implementations are unfortunately both common and not terribly useful.

I’m sorry. You’re right.

accept a PR implementing this

by the way, what does “PR” mean?

PR = “pull request

yes, it works.:grinning:

PR = “pull request

I see. thanks.