if
block does not introduce a new scope.
Here is a version where f
is defined in a hard scope for both if/else branches:
function create_f(flag::Bool)
function locf end
if (flag)
let f(x) = x^2
locf = f
end
else
let f(x) = x^3
locf = f
end
end
return locf
end
Now, both create_f(true)(x)
and create_f(false)(x)
are going to behave as expected.
Also, consider the following:
function nested(flag::Bool)
f(x) = x
if flag
f(x) = x + 1
end
return f
end
f = nested(false)
f(2) # will produce 3
The thing is that the method/function definition takes place before the runtime call. Also, the method overriding takes place because if
doesn’t introduce a new scope.
However, I am not (yet) able to explain why the else
(flag = false
) branch in the original function throws UndefVarError:
f not defined
.
Later addition:
So there might be a bug after all:
function create_f(flag::Bool)
if flag
f(x) = x^2
else
f(x) = x^3
end
return f
end
@code_lowered create_f(true)
@code_lowered create_f(false)
@code_lowered
produces:
CodeInfo(
1 ─ Core.NewvarNode(:(f))
└── goto #3 if not flag
2 ─ f = %new(Main.:(var"#f#7"))
└── goto #3
3 ┄ return f
)
So, when flag = false
, it just skips the f
definition and attempts to return it. This behavior has nothing to do with overriding the method in the else
branch (the same issue occurs if a different method signature is used).
We can compare with the following naive example:
function notbug(flag::Bool)
if flag
x = 3
else
x = 4
end
return x
end
@code_lowered notbug(true)
@code_lowered notbug(false)
… which produces (as expected):
CodeInfo(
1 ─ Core.NewvarNode(:(x))
└── goto #3 if not flag
2 ─ x = 3
└── goto #4
3 ─ x = 4
4 ┄ return x
)