Invoke different method for callable struct / How to emulate pythons `super()`

I have a parametric function as discussed here and now want to generate another such function type that logs its arguments while invoking the non-logging method for the computation. How do I use invoke to dispatch on the type of f and not on argtypes?

If that is all a bit convoluted, here is a MWE

abstract type AbstractAdder <: Function end

# the non-logging version
struct Adder <: AbstractAdder
    a::Int
end

# general implementation for all adders
function (f::AbstractAdder)(x::Int)
    return f.a + x
end

# the logging version
struct TrackingAdder <: AbstractAdder 
    a::Int
    log::Vector{Int}
end

# special implementation for the logging version.
# It should just push `x` to `f.log` and then call the general
# method to do the actual computation
function (f::TrackingAdder)(x::Int)
    push!(f.log, x)
    # how to use `invoke` here s.t. it uses the general method for `AbstractAdder`?
    return invoke((), Tuple{AbstractAdder, Int}, x)
end
1 Like

While I am still waiting for answers, here is what I do for now:

abstract type AbstractAdder <: Function end

# the non-logging version
struct Adder <: AbstractAdder
    a::Int
end

@inline function _call(f::AbstractAdder, x::Int)
    return f.a + x
end

(f::AbstractAdder)(x::Int) = _call(f, x)

# the logging version
struct TrackingAdder <: AbstractAdder 
    a::Int
    log::Vector{Int}
end

function (f::TrackingAdder)(x::Int)
    push!(f.log, x)
    return _call(f, x)
end

I think that you could add dummy unused singleton arguments to either the struct or the abstract type function or both. So you ignore the value, and just let Julia use the type of the arg for dispatch.

So something like this:

abstract type AbstractAdder <: Function end

abstract type DisambigSingleton end
struct DisambigAbstractAdder <: DisambigSingleton end
struct DisambigTrackingAdder <: DisambigSingleton end

# the non-logging version
struct Adder <: AbstractAdder
        a::Int
end

# general implementation for all adders
function (f::AbstractAdder)(x::Int, ::DisambigAbstractAdder)
        return f.a + x
end

# the logging version
struct TrackingAdder <: AbstractAdder
        a::Int
        log::Vector{Int}
end

# special implementation for the logging version.
# It should just push `x` to `f.log` and then call the general
# method to do the actual computation
function (f::TrackingAdder)(x::Int, ::DisambigTrackingAdder)
        push!(f.log, x)

        return invoke(f, Tuple{Int, DisambigAbstractAdder}, x, DisambigAbstractAdder())
end

REPL usage example:

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.4 (2021-03-11)
 _/ |\__'_|_|_|\__'_|  |
|__/                   |

julia> include("sup.jl")

julia> a = TrackingAdder(5, Int[])
(::TrackingAdder) (generic function with 2 methods)

julia> a(3, DisambigTrackingAdder())
8

julia> a.log
1-element Array{Int64,1}:
 3

I think it would be possible to make this prettier with some wrappers, but I’m guessing you’d do best to adopt a different approach. There is probably a more “Julian” solution to your problem.

EDIT: I think an improvement might be to make DisambigSingleton a parametric struct, parameterized by, e.g., Int, then the “super” operation could perhaps be more automatic (by doing something like DisambigSingleton{N + 1} (or DisambigSingleton{N - 1}) in the methods instead of super.

Yep, that works. I modified the idea a bit more by making the singleton a default argument

function (f::AbstractAdder)(x::Int, _::DisambigAbstractAdder=DisambigAbstractAdder())
        return f.a + x
end

function (f::TrackingAdder)(x::Int, _::DisambigTrackingAdder=DisambigTrackingAdder())
        push!(f.log, x)
        return invoke(f, Tuple{Int, DisambigAbstractAdder}, x, DisambigAbstractAdder())
end

because having to pass the singleton manually pretty much defeats the purpose of the whole exercise.

2 Likes