Scope and Inlining

I stumbled across a case of bad practice that resulted in an error, but I am not sure why.

function g(y)
    function f(x)
        f = x * y
        return f
    end

    f(y)
    f(y)
end

g(0.2)

I really should not define a variable f within a function f. The about code throws ERROR: LoadError: MethodError: objects of type Float64 are not callable and points to the second call of f within g.

At that time, f is a Float64 but why? Does that have to do with inlining? Calling f once instead of twice within g works out fine.

1 Like
julia> f = 1.0
1.0

julia> x = 2.0
2.0

julia> f(x)
ERROR: MethodError: objects of type Float64 are not callable

when you do f(y), f becomes the result of f = x*y which happens to be a Float64. And you are then trying to “call” that float with parameter y.

julia> function g(y)
           function f(x)
               f = x * y
               return f
           end
           println(f," ",typeof(f))
           f(y)
           println(f," ",typeof(f))
           f(y)
       end
g (generic function with 1 method)

julia> g(2)
f var"#f#5"{Int64}
4 Int64
1 Like

This has nothing to do with inlining and is entirely due to how local scope works. Function g introduces a local scope. It has a local variable f, which is initially bound to a function. Now inside that function, you also assign to f. Because this is all inside a local scope, that inner f assignment doesn’t shadow the outer f function (like it would if this inner function were defined at global scope) and instead it is the same binding. Thus a side-effect of executing that inner function is changing what f refers to.

3 Likes

A simpler example of the same rules in action would be:

julia> let z = 0
           let
               z = 1
           end
           z
       end
1

versus

julia> let
           let
               z = 1
           end
           z
       end
ERROR: UndefVarError: z not defined
Stacktrace:
 [1] top-level scope
   @ REPL[25]:5