Function closures and ifelse with unexpected behaviour

Hello,

I am quite confused with the following behavior of this function:

function switchcase(i, str)

    case = str
    if uppercase(case) == "A"
    	@info "set case A"
       showcase(i) =  @printf("case A number %i", i)
    elseif uppercase(case) == "B"
    	@info "set case B"
       showcase(i) =  @printf("case B number %i and some more parameters %i", i,8)
    else
        @error("no case $case")
    end
    showcase(i)
end

where outputs are as following:

julia> switchcase(2, "A")
[ Info: set case A
case B number 2 and some more parameters 8
julia> switchcase(2, "B")
[ Info: set case B
ERROR: UndefVarError: showcase not defined
Stacktrace:
 [1] switchcase(::Int64, ::String) at ./REPL[224]:13
 [2] top-level scope at REPL[226]:1

My intention is to use closures to define different perturbation sources in a bigger simulation code. Given an inputs file, the correct perturbation model is called.

Is it perhaps related to this post? Surprising capture boxing behavior in closure

I would be thankful for any hint to understand this outcome.

See this previous question which boils down the same problem.

2 Likes

I wandered if this alternative works, in which the functions are defined before and the conditional only assigns a new name, but I get a segmentation fault error. Any comment on that?

julia> using Printf

julia> showcaseA(i) = @printf("case A number %i", i)
showcaseA (generic function with 1 method)

julia> showcaseB(i) = @printf("case B number %i and some more parameters %i", i,8)
showcaseB (generic function with 1 method)

julia> function switchcase(i, str)
           case = str
           if uppercase(case) == "A"
                   @info "set case A"
              showcase = showcaseA
           elseif uppercase(case) == "B"
                   @info "set case B"
              showcase =  showcaseB
           else
               @error("no case $case")
           end
           showcase(i)
       end
switchcase (generic function with 1 method)

julia> switchcase(2, "A")

signal (11): Segmentation fault
in expression starting at REPL[6]:1
operator() at /buildworker/worker/package_linux64/build/src/codegen.cpp:6101 [inlined]
emit_function at /buildworker/worker/package_linux64/build/src/codegen.cpp:6211
jl_emit_code at /buildworker/worker/package_linux64/build/src/codegen.cpp:6431
jl_emit_codeinst at /buildworker/worker/package_linux64/build/src/codegen.cpp:6465
_jl_compile_codeinst at /buildworker/worker/package_linux64/build/src/jitlayers.cpp:97
jl_generate_fptr at /buildworker/worker/package_linux64/build/src/jitlayers.cpp:302
jl_compile_method_internal at /buildworker/worker/package_linux64/build/src/gf.c:1964

Yet, a simpler version of this trial works:

ulia> f(i) = i + 1
f (generic function with 1 method)

julia> g(i) = i + 2
g (generic function with 1 method)

julia> function h(i)
         if i == 1
           s = f
         else
           s = g
         end
         s(i)
       end
h (generic function with 1 method)

julia> h(1)
2

julia> h(2)
4

2 Likes

I was just poking at that — if you remove the @info and @error lines, the segfault goes away.

2 Likes

Actually removing only the @ from @error solves that:

julia> showcaseA(i) = @printf("case A number %i", i)
showcaseA (generic function with 1 method)

julia> showcaseB(i) = @printf("case B number %i and some more parameters %i", i,8)
showcaseB (generic function with 1 method)

julia> switchcase(2, "A")^C

julia> function switchcase(i, str)
                  case = str
                  if uppercase(case) == "A"
                          @info "set case A"
                     showcase = showcaseA
                  elseif uppercase(case) == "B"
                          @info "set case B"
                     showcase =  showcaseB
                  else
                      error("no case $case")
                  end
                  showcase(i)
              end
switchcase (generic function with 1 method)

julia> switchcase(2, "B")
[ Info: set case B
case B number 2 and some more parameters 8
julia> switchcase(2, "A")
[ Info: set case A
case A number 2
julia> switchcase(2, "C")
ERROR: no case C

The @error macro makes the error be non-fatal, and the function continues to evaluate the showcase function, which will not be defined in that case.

If we add any assignment for showcase after the error, the segmentation fault disappears, and one gets a sensible error message:

                  else
                      @error("no case $case")
                      showcase = nothing          
                  end

julia> switchcase(2, "C")
┌ Error: no case C
└ @ Main REPL[7]:8
ERROR: MethodError: objects of type Nothing are not callable
Stacktrace:
 [1] switchcase(::Int64, ::String) at ./REPL[7]:11
 [2] top-level scope at REPL[10]:1

The correct error message should be, without the definition of showcase after the error:

julia> switchcase(2, "C")
┌ Error: no case C
└ @ Main REPL[11]:6
ERROR: UndefVarError: showcase not defined
Stacktrace:
 [1] switchcase(::Int64, ::String) at ./REPL[11]:8
 [2] top-level scope at REPL[12]:1

Which is actually what we get if removing, for example, the elseif ... from the code. I think that that segmentation fault is a bug somewhere:

julia> function switchcase(i, str)
                  case = str
                  if uppercase(case) == "A"
                     showcase = showcaseA
                  else
                      @error("no case $case")
                  end
                  showcase(i)
              end
switchcase (generic function with 1 method)

julia> switchcase(2, "C")
┌ Error: no case C
└ @ Main REPL[11]:6
ERROR: UndefVarError: showcase not defined
Stacktrace:
 [1] switchcase(::Int64, ::String) at ./REPL[11]:8
 [2] top-level scope at REPL[12]:1


2 Likes

I’ve filed an issue with Julia that includes the fancy-new --bug-report=rr tracing feature.

2 Likes

Yeah, you’re completely right. Also, trying an interactive @run switchcase(2, "C") with Debugger.jl stops the segfault, but it’s still stalling within the logging (in a way I don’t completely understand since it seems to be during the elseif branch and not the else branch as I expected… But that could just be another symptom of something malformed.)