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.
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).
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.
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