Surprising behavior when function contains variable with same name

I have been running into issues with Julia functions whose name matches the name of an internal variable. For example, the following function seems pretty harmless to me:

function speed(distance,time)
    speed = distance/time
    return speed
end

And indeed this function works just fine:

speed(6,2)
3.0

However, I ran into an issue today where referencing the name of the function in the function caused an error. The following is a MWE:

using DifferentialEquations
function MyCalculation()
    function ImportantParameter(z)
        ImportantParameter = z^2
        return ImportantParameter
    end
    
    function dudz!(dudz,u,p,z)
        dudz[1] =  ImportantParameter(z)*u[1]
    end
    
    u0 = [1.0]
    zspan = (0.0, 0.1)
    IVPprob = ODEProblem(dudz!, u0, zspan)
    return solve(IVPprob, Tsit5())
end

MyCalculation()

This gives an error:

MethodError: objects of type Float64 are not callable
Maybe you forgot to use an operator such as *, ^, %, / etc. ?

On the other hand, if we change the name of the variable as follows, no error is reported:

function MyCalculation()
    function ImportantParameter(z)
        ImportantParameter_different_name = z^2
        return ImportantParameter_different_name
    end
    
    function dudz!(dudz,u,p,z)
        dudz[1] =  ImportantParameter(z)*u[1]
    end
    
    u0 = [1.0]
    zspan = (0.0, 0.1)
    IVPprob = ODEProblem(dudz!, u0, zspan)
    return solve(IVPprob, Tsit5())
end

MyCalculation()

It is surprising to me that changing a local variable name inside a function would affect the behavior of the function in this way. I’m curious if this behavior is expected? It is also strange to me that sometimes using the name of the function as a variable name is harmless, but on other occasions it causes errors; I haven’t figured out a pattern as to when it is safe and when it is not.

Thanks!

1 Like

The rule is actually simple: nested local scopes reuse outer local variables by default. In your first example, the outer speed is a const global variable, so it is not reused in its local scope. In your second example, the outer ImportantParameter is a local variable in the scope of MyCalculation, so it does get reused by its inner local scope. Unfortunately that reuse is a reassignment from the function to z^2, so your first function call costs you access to that very function. To override this default behavior, declare a new local variable local ImportantParameter = z^2 in the nested scope, though you probably should just return z^2 in such a simple case or more generally use a different meaningful name.

4 Likes

Looks like another good reason to implement this proposal.

I’m not convinced implicitly consting local function names is a good idea. They don’t even stop reassignments in the global scope now, instead it prints “warning: redefinition of constant” and lets weird things happen. At least throwing “not callable” errors stops it from getting very far. Even if you make const variables truly unreassignable, it’d still throw an error when attempting to reassign them. Unless you break the “local scopes reuse outer local variables” rule thus adding even more exceptions to the scoping rules, it’s good to encourage people to make meaningful distinct names.