Extract argument names

question

#1

I am trying to figure out the API for extracting argument names from (a method of a) function. What I want to achieve roughly looks like this:

  1. care about positional argument names only, keywords are ignored
  2. functions with one method: get argument names for that one
  3. functions with more than one method: make the user pick with which
  4. should work for closures (they always have one method, right)?

I can live with the API for this being in flux, the library I am working on will test for my use cases so I can keep up with changes (I hope). But of course the more stable the better.

After reading the sources and looking at dumps of various things, I came up with this:

# always try to find our way to this case
argument_names(li::LambdaInfo) = li.slotnames[2:end]

# method: unambiguous, use the lambda_template
argument_names(m::Method) = argument_names(m.lambda_template)

# this works for function names
function argument_names(f::Function)
    m = methods(f)
    length(m) == 1 || error("use `which` to pick from multiple methods")
    argument_names(m[1])
end

# this works for closures
function argument_names{T <: Function}(l::T)
    m = code_lowered(l)
    length(m) == 1 || error("use `which` to pick from multiple methods")
    argument_names(m[1])
end

# tests
foo(x::Int) = 2*x
argument_names(which(foo, (Int,))) # picking a method
argument_names(foo)                # unambiguous
argument_names(x->2*x)             # closures OK
argument_names((x;y=9) -> x*y)     # keyword arguments OK

Questions:

  1. is this roughly correct, or am I doing something stupid? please correct me.
  2. is there an API already in place for what I am doing? maybe someone worked out a library for this?

Motivation (just in case you are curious): allow a user to apply a function f do an associative collection Symbol => Vector elementwise, picking the appropriate arguments from the collection. So

dostuff((x,y) -> x*y, collection) # with a closure

and

function thank_heavens_I_debugged_this(x,y)
    x*y
end
dostuff(thank_heavens_I_debugged_this, collection)

should work. I am aware that various libraries use something like this, if you can point to a “canonical” implementation I could copy that would also help.


#2

No, you can add methods to an anonymous function using call overloading

f = (x) -> x^2
(T::typeof(f))(x,y) = x^2 + y^2

Invoke a method
#3

That looks roughly right. The closest we have to a reference implementation is probably https://github.com/JuliaLang/julia/blob/bfc72b6fe913236ccb2bdd1ef254bceccb0cc31a/base/methodshow.jl#L35 which handles printing methods.
And as you noted, you generally need a concrete signature (e.g. the result of which) to have only a single answer. You shouldn’t dispatch on ::Function since everything is callable, the functions you wrote will work on Any object type. However, if you don’t have a single Method, the most you can do is iterate over the methods list to aggregate the answer over all of the associated methods.