`methods` and `@which` give different results

I have:

f(; x) = 0

and I do:

f(x = 1)

creating one specialized method (MethodInstance?) of f.

I can see the specialized method with a @which:

julia> (@which f(x = 1)) |> Base.specializations
Base.MethodSpecializations(MethodInstance for Core.kwcall(::@NamedTuple{x::Int64}, ::typeof(f)))

But using methods shows me nothing:

julia> f |> methods |> only |> Base.specializations
Base.MethodSpecializations(svec())

@which is showing me a method of kwcall:

julia> (@which f(x = 1))
kwcall(::NamedTuple, ::typeof(f))

but methods shows me something else:

julia> methods(f) |> only
f(; x)

This does not hold:

(@which g(1)) == only(methods(g))

How can I reliably get all methods of a function and each method’s specializations?

This seems specific to functions with keyword arguments. If I have:

g(x) = 0
g(1)

This holds:

(@which g(1)) == only(methods(g))

The method which is called for f(; x=1) is Core.kwcall(...) as correctly given by @which. However, that method is not a method of function f but a method of function Core.kwcall, as indicated by its name, so it won’t appear in methods(f). Actually, the only method that appears in methods(f) is that which is called with f(), which throws an UndefKeywordError error: you can indeed check that:

julia> (@which f()) == only(methods(f))
true

The full details of the inner workings of keywods arguments are given in the dev docs: Julia Functions · The Julia Language
and as you can see there, whenever a method with keyword arguments is defined in the code, multiple methods are actually defined, so if you wanted to collect all the relevant methods for your example case here, there would be the one in methods(f), the Core.kwcall one you can get from @which, and a last one found as methods(var"#f#1")… however the number at the end (here 1) depends on how many keyword argument methods were defined before, and it is an implementation detail of the compiler, I don’t know if there is a proper way to retrieve that name var"#f#1".

2 Likes

I’m taking that to mean that there’s no reliable way to get a list of all method specializations.

Also, to make things more confusing, the kwcall method (correctly?) has the name :f.

(@which f(x = 1)).name == :f

Or maybe this helps, actually. I could get a list of all kwcall methods with name :f.

It’s not exactly a matter of finding the method specializations but rather finding the method itself. But even though I can’t really recommend it as part of a workflow, you can always do:

julia> f(; x) = 2x
f (generic function with 1 method)

julia> f(y; x1, x2) = y*(x1 + 2x2)
f (generic function with 2 methods)

julia> kwfunctions = eval.(filter(x -> startswith(String(x), "#f#"), names(Main; all=true, imported=false)))
2-element Vector{Function}:
 #f#1 (generic function with 1 method)
 #f#2 (generic function with 1 method)

julia> [Base.specializations(only(methods(x))) for x in kwfunctions]
2-element Vector{Base.MethodSpecializations}:
 Base.MethodSpecializations(svec())
 Base.MethodSpecializations(svec())

julia> f(; x=36)
72

julia> [Base.specializations(only(methods(x))) for x in kwfunctions]
2-element Vector{Base.MethodSpecializations}:
 Base.MethodSpecializations(MethodInstance for var"#f#1"(::Int64, ::typeof(f)))
 Base.MethodSpecializations(svec())

julia> f(14; x1=13.6, x2=4//5)
212.79999999999998

julia> [Base.specializations(only(methods(x))) for x in kwfunctions]
2-element Vector{Base.MethodSpecializations}:
 Base.MethodSpecializations(MethodInstance for var"#f#1"(::Int64, ::typeof(f)))
 Base.MethodSpecializations(MethodInstance for var"#f#2"(::Float64, ::Rational{Int64}, ::typeof(f), ::Int64))
1 Like

Method specializations are internal details of the language’s implementation, which are subject to changes across versions and aren’t part of the language’s standard. If you make base language API that “gets a method’s specializations”, you effectively force method specializations into the language standard and limit the implementations. This sort of reflection will at best be an unstable, implementation-specific, third-party library.

1 Like

That’s right, and actually even Base.specializations is not part of the public API, as you can see from the REPL documentation:

help?> Base.specializations
  │ Warning
  │
  │  The following bindings may be internal; they may change or be removed in future versions:
  │
  │    •  Base.specializations

I find this kind of question very interesting to learn how the internals of the language work, but you can’t rely on the hack I showed before (or even Base.specializations thus) for production purpose.

1 Like

I appreciate the caution that I’m venturing into non-public API. I was just trying to learn about methods and dispatch vs specialization, and am not relying on these internals for production.