Finding possible keyword arguments

This is inspired by this post:
Is there a way of finding allowed keyword arguments for a function (e.g. bar) that passes its kwargs to another function, such as

foo(;a=2,b=3) = a+b
bar(;kwargs...) = foo(; kwargs...)

so that I can find that kwargs should be a or b ? This would certainly nice for a lot of documentation. I would love to have

julia> @doc bar
  No documentation found.

  bar is a Function.

  # 1 method for generic function "bar":
  [1] bar(; a, b) in Main ...

or

julia> allowedkwargs(bar)
(:a,:b)

I know it can get awfully complicated, but nevertheless would be pretty handy in many cases.

3 Likes

Maybe DocStringExtensions could add something like that?

I had a quick look at DocStringEstensions.jl and it might be possible to exploit it to do this. I will check if I can make use of (i.e. understand) their code to write the function in need.

Splicing keyword arguments is OK for exploratory code, but for a more polished API consider

"""
Here we document `Things` nicely.
"""
Base.@kwdef struct Things{T,S}
    a::T = 2 
    b::S = 3
end

foo(things) = things.a + things.b
bar(things = Things()) = foo(things) # could also use kwargs
1 Like

Okay, I’ve never seen that being used to be honest :grimacing:. And mostly my goal is to find keyword arguments when someone hasn’t done a good job at polishing the API.

+1. In addition to it being hard to find keyword arguments for a function, there can be pretty bad consequences for passing in the wrong or misspelled keyword arguments in some cases. For example, when a function has a kwargs... slurp and doesn’t use those the kwargs, no errors will be thrown for misspelled keyword arguments. Example:

julia> badfun(;x=1, y=2, kwargs...) = x*y^2
badfun (generic function with 1 method)

julia> badfun(;y=5)
25

julia> badfun(;why=5)
4

True. I never thought about really bad consequences of that. I just often tried to find the right kwarg for changing a plot property in Plots.jl and it doesn’t give an error when you didn’t find the right one…

Yeah, Plots.jl is where I run into it the most. Thankfully there it tends not to be very high-stakes if you get it wrong. You can usually just tell by looking at the plot that your argument didn’t get applied. With other more critical packages, it could cause more serious issues.

Functions do not have keyword arguments. Methods do. Eg in

f(x::Int; a = 1, b = 2) = ...
f(x::String; c = 7) = ...
f(x::Float64, y::Char) = ...

it is unclear what one would mean by “the keyword arguments of f”. The union? Or the intersection?

This may sound pedantic, but it is key to idiomatic API design in Julia. Ideally, keyword arguments should be in a single interface method that takes care of them from then on, and should be documented there.

Eg in @fgerick’s example above, bar would document a and b, and the either user would not need to know about foo at all (an implementation detail), or it should be documented separately in its own docstring. For repeated lists of keyword arguments (or anything), one can write a string and interpolate it into multiple docstrings. But the cleanest approach is probably a wrapper type.

2 Likes

Right. Methods have keyword arguments. I misspoke there, but that doesn’t change the main issue. There are packages out there that have kwargs... slurps in methods without using the kwargs and will therefore fail silently with misspelled keyword arguments. Combine that with the fact that keyword arguments are often not documented well and there is a problem.

Yes, I run into these things from time to time, and understant that this can be quite frustrating. I consider this a documentation issue in the narrow sense, or an API design issue in the broader context.

I also realize that “please redesign your API” is not the ideal way to start an issue/PR. But most packages do review their API from time to time and welcome feedback. Until then, a PR to fix the docstrings is usually helpful.

I think there are two discussions here:

  1. How can we avoid this caveat in the first place by making better API or coding choices?
  2. How can we exploit what has been done to find the kwargs that have not been documented or foolishly implemented in the past?

I personally don’t want to try to solve 1 but I’m happy if this thread will trigger some API changes to avoid this problem. When I find the time I start to read what can be done for 2.

I just ran into this the other day when I typed complicated_function(a, b, c; <TAB><TAB> and was hoping to see all the kwargs that are available. This should be possible for non-slurpy methods since the REPL knows what positional args have been given — and the REPL already shows us the positional-arg methods if you hit tab after a , without kwargs!

2 Likes

I think for #2, the easiest immediate action would be to add a check for unused keyword arguments to Lint.jl. As of right now, I think it only warns on unused positional arguments. It would be nice if it also had an extra serious warning for an unused keyword argument slurp.

2 Likes

up until recently Plots.jl acutally did warn on unsupported keyword arguments.
I did hijack this on purpose to be able to pass arbitrary keywords to the backend.

I do the same in Gaston.jl. My code doesn’t know what keywords are accepted by the backend (Gnuplot in my case), and doesn’t care.

I am not sure what you are expecting here. In your original example, it may be the programmer’s intent that bar should take a and b as keyword arguments, but I am not sure how the language or the tooling can figure it out. Consider an API where the default fallback is

bar(x::SomeType; kwargs...) = foo(x; kwargs...)

but third party types (in other packages) are invited to implement their own method — basically anything can happen.

I still think that this is a documentation issue. For a particular bar, the solution is a better docstring.

1 Like

I know it is not so straight forward and I was sure there are a lot of reasons why it is not already solved. Maybe there can be a macro, such as @which, so I can find the right keyword arguments for foo(x::SomeType; kwargs...), defined at the position that @which spits out?

This PR about adding fuzzy finding to Plots.jl might make finding the right keyword easier.