Method overloading doesn't work inside a function

The following method overloading works:

julia> VERSION
v"1.8.0"

julia> myfun1(x, y) = x+y  # method to overload
myfun1 (generic function with 1 method)

julia> myfun1(x) = myfun1(x, 1)  # overloading
myfun1 (generic function with 2 methods)

julia> myfun1(3)  # no error
4

However, inside another function, if I try to overload a method defined outside the function, it fails:

julia> myfun2(x, y) = x+y  # method to overload
myfun2 (generic function with 1 method)

julia> function wrapper(x)
           myfun2(x) = myfun2(x, 1)  # overloading
           val = myfun2(x)
           return val
       end
wrapper (generic function with 1 method)

julia> wrapper(3)  # error
ERROR: MethodError: no method matching (::var"#myfun2#3")(::Int64, ::Int64)
Closest candidates are:
  (::var"#myfun2#3")(::Any) at REPL[8]:2
Stacktrace:
 [1] (::var"#myfun2#3")(x::Int64)
   @ Main ./REPL[8]:2
 [2] wrapper(x::Int64)
   @ Main ./REPL[8]:4
 [3] top-level scope
   @ REPL[9]:1

Why does this not work? Is there a way to make this work?

This is not overloading — you’re declaring a new local function myfun2 that happens to have the same name (shadows) the global function myfunc2, in the same way that typing x = 1 in a function does not overwrite a global variable named x unless you use global.

The only way to do this in a function is to use eval, e.g.:

function wrapper(x)
    @eval myfun2(x) = myfun2(x, 1)  # overloading
    return Base.invokelatest(myfun2, x)
end

However, it’s pretty rare that you should actually need to do this. What are you trying to accomplish?

Thanks for the explanation! I was not trying to accomplish anything exotic; I just wanted to define an inner function myfun2(x) that would play a nearly identical role as the outer function myfun2(x, y) except for a fixed parameter y = 1. I didn’t want to give a new name to the inner function because its role would be similar to the outer function’s, but I was surprised that it didn’t work.

But I still don’t quite understand why Julia is designed to generate an error in my attempted use case. Even though the inner function name shadows the outer function name, its signature is different with a different number of arguments, so isn’t there enough information to discriminate the inner function call from the outer function call? The dispatch system will get confused if I additionally define myfun2(x) outside wrapper() later. Maybe the design decision was made to avoid such confusion?

I think the design choice here is that scoping rules for function names is no different than for other variables. Your call to myfun2(x, 1) resolves to the myfun2 defined within wrapper as stevengj pointed out. There is no special case that would say, "if the local scope function found does not have the same signature as the call, then look in an outer scope`. Consistency makes scoping reasoning easier for both the user and the compiler.

2 Likes

Have you seen Base.Fix1 and Base.Fix2?

3 Likes

To elaborate a little on what others have said: the myfun2 inside your function is not the same function as the myfun2 outside of it. They live in different scopes. What you’re trying fails for the same reason that you can write

function f(x)
  sum = zero(eltype(x))
  for xx in x
    sum += xx
  end
  return sum
end

without obliterating the sum function everywhere.

I think the Base.Fix2 suggestion is a good solution for this example. Otherwise, name your inner function something else to avoid this shadowing. Alternatively, move your inner function definition to the outer scope (although perhaps you didn’t want that default value to apply in general, hence your inner definition attempt).

You could always use x -> myfun2(x, 1), or define myfun2_1(x) = myfun2(x, 1). (I hope someday we can get the syntax myfun2(_, 1), but I’m not holding my breath.)

The basic issue is that if you were to add methods to the global function myfun2 itself, it would affect everything using myfun2. That’s why it can only be done using eval, and probably isn’t a good idea.