Confused by behaviour of nested functions


#1

Dear Julia users,

I must confess to finding the following behaviour extremely puzzling - I suspect it’s related to scoping, but I can’t make heads or tails of it. Here’s a simple function featuring nested functions:

function test1(arg)
    if (arg==1)
        f(x) = 3*x
        f(1)
    else
        a = 5
        f(x) = a*x
        f(2)
    end
end

Now calling test1(1) gives me:
ERROR: UndefVarError: a not defined

and test(2) gives me
ERROR: UndefVarError: f not defined

Executing the above code directly in the REPL works fine, though, ie:

arg = 1
if (arg==1)
    f(x) = 3*x
    f(1)
else
    a = 5
    f(x) = a*x
    f(2)
end

Here’s a variant that fails in a different manner:

function test2(arg)
    f(x) = x
    if (arg==1)
        f(x) = 3*x
        f(1)
    else
        a = 5
        f(x) = a*x
        f(2)
    end
end

Now test2(1) still fails with the same error but test2(2) works.

Finally, this variant works fine:

function test3(arg)
    if (arg==1)
        f  = (x) -> 3*x
        f(1)
    else
        a = 3
        f = (x) -> a*x
        f(2)
    end
end

This on julia 1.0.3. What gives?

Thanks!


#2

Don’t define functions conditionally inside other functions: https://github.com/JuliaLang/julia/issues/15602.


#3

All right, thanks!


#4

Just in case it wasn’t clear, using anonymous functions (as your test3 does) is the recommended way to avoid this problem. There is also no performance penalty for anonymous functions in Julia, so it should work quite well.


#5

Just curious, but in this case why not simply forbid non-anonymous nested functions? The current behaviour is super confusing for beginners, especially from lisp-y languages like R.


#6

Good question. The problem only occurs when you have conditionally defined nested functions, like this:

function foo()
  # which definition of f() will occur? who knows?
  if rand() < 0.5
    f(x) = x + 1
  else
    f(x) = x + 2
  end
  f(1)
end

If the definition isn’t inside a conditional (like the if block above), then everything works:

function foo()
  # this if fine
  f(x) = x + 1
  f(1)
end

But I totally agree that the former behavior is confusing. I would support making the former an error (or, of course, making it actually work as expected). Such a change would technically be breaking, though, so it might not be possible until Julia 2.0. I’d have to leave that question to the core devs.


#7

Yes, this is a long-standing bug I would like to fix. It arises from a combination of

  1. Functions can have multiple methods
  2. The syntax for adding a method and creating a new function is the same
  3. We insist that the set of methods in a function only change at the top level

What we don’t want, for example, is for f to have different methods for different argument values to test1. Given that, it’s not clear what the code should mean and it should probably be an error, but it’s not clear what the rules should be exactly.

We don’t want to entirely forbid non-anonymous nested functions, since (other than this issue) it’s fairly easy to support nested functions with multiple methods.