Extend methods from another module while calling the original method within the extended method


I wonder if it’s possible to extend a method from another module while calling the original method within the extended method.

For example,

function MyPkg.myfunc(x; t)  # extended method
    2*MyPkg.myfunc(x; u=t)  # original method's form: myfunc(x; u)

Of course, the above method does not work properly.

What should I do?

EDIT: I wanna preserve the name myfunc if possible.

1 Like

One option:

julia> module Foo
         myfunc(x) = println("Foo original");

julia> import .Foo.myfunc as foo_func

julia> foo_func(1)
Foo original

julia> myfunc(x) = println("Extended", foo_func(1))
myfunc (generic function with 1 method)

julia> myfunc(1)
Foo original

Whether this is a good idea is another matter.
You are not really extending myfunc; you are replacing it.
This might break things.


Caveat, I really wouldn’t recommend doing either of these things, they’re brittle (especially the second one) and probably can easily break / lead to bugs. But they are technically possible.

If you’re specializing the arguments of the function beyond the original definition, you can call the original definition with Base.invoke (or the macro form in Julia 1.7):

julia> foo(x::Real) = "original"
foo (generic function with 1 method)

julia> foo(x::Int) = ("new", Base.invoke(foo, Tuple{Real}, x))
foo (generic function with 2 methods)

julia> foo(1)
("new", "original")

If you’re not specializing, but just overwriting the method, you can still call the old version by invoking it in the world age before your new version was defined:

julia> original_world = ccall(:jl_get_world_counter, UInt,())

julia> foo(x::Real) = ("new", Base.invoke_in_world(original_world, foo, x))
foo (generic function with 2 methods)

julia> foo(1.)
("new", "original")

You’re right.
From your answer, I realised that what I want is close to replacing the imported method.
According to the answers, it would potentially be harmful.

Then, what would be the best way of replacing imported methods? (If necessary)

EDIT: for example, if I want to preserve the name of imported method while modifying the imported method, what would be the best practice?

I don’t know. Both seem to work. Is there a benefit to using the (more complicated) world age approach?

1 Like

If you just want a function with the same name, but which is not a method of the original function, you just need to not import the other function into your namespace and instead refer to it explicitly, e.g. if you want to swap argument order or something:

import OtherPackage
myfunc(x,y) = OtherPackage.myfunc(y,x)

As reading all comments, the notions of function, namespace, and method becomes clear.
Thanks a lot!

Actually, I intended to replace the existing method of the original function (so this does not fit in my case).

1 Like

Could you provide some context as to what you are trying to achieve?
I conjecture there may be a different design pattern that would serve you better than replacing a method in a package.

1 Like

Sure :slight_smile:

In my case, a package MyPkg exports a function my_func with method my_func(multicopter::Multicopter).
I would like to preserve the method my_func(multicopter::Multicopter) while it acts slightly differently.
For example, I import the method my_func(multicopter::Multicopter) and reuse it for custom multicopter models with additional features, e.g., fault models.

Have you considered defining a new Multicopter type for this? I.e.,

module Copters
# Hopefully there is an abstract type
abstract type AbstractHelicopter end
struct Multicopter <: AbstractHelicopter
my_func(m :: AbstractHelicopter) = [...]
end # module

using .Copters
struct FancyCopter  <: AbstractHelicopter
function my_func(m :: FancyCopter)
  # do some FancyCopter specific extra stuff
1 Like

IMO, it’s the best remedy so far.

BTW, to inherit other methods for Multicopter, how about making FancyCopter a sub-type of Multicopter and defining a specific method my_func(m::FancyCopter)?

I assumed that Multicopter was a concrete type. Otherwise, yes.

1 Like