How to insert hooks into arbitrary places

I want to log every call of f(::T). I don’t know at design time what the implementation of f(::T) is, so I can’t reproduce it.

One idea is to save m = which(f, Tuple{T}) and then insert a hook with f(x::T) = (log(x); invoke_method(m, x) and delete the hook with Base.delete_method(which(f, Tuple{T})); Base.insert_method(m), but I don’t know how to invoke_method or insert_method.

How is this done in Julia?

You could do that “the other way around”, write a decorator for F and use that instead of f?

struct LogFCall{F}
    f::F
end
function (f_l::LogFCall)(x)
    println("I do some logging")
    return f_l.f(x)
end
# A function f
f(x) = x+3
# but we use this function instead in all places
f_l = LogFCall(f)

then

julia> f_l(4)
I do some logging
7

and f_lwould be what you use in all places you before used f. And sure you could write this functor also with a list of things to do that you can add or remove (instead of just doing a print doing set of functions then).

So in Hooks, LogFCall is a HookMemory around f.

Is that maybe something that helps?

1 Like

That’s not what I’m looking for. I want this to be generic over arbitrary code and not require editing the downstream code to call my functions (e.g. when tracking getindex, I can’t exactly replace every call to getindex in the base library to l_getindex).

Wrapping the arguments with a decorator type is not what I’m looking for for similar reasons.

1 Like

Cassette helps?
https://julia.mit.edu/Cassette.jl/stable/overdub/

2 Likes

The boring answer is - we have Cassette.jl for that, but the caveat with that is tons and tons of invalidations, compile times and general dislike for magic solutions that reach deep into existing code without a controlling dispatch :person_shrugging: