Nothing == absence of keyword argument?

Is there a convenient idiom to indicate that a keyword argument is absent? I imagine that somebody may have already invented a macro or something to do this . . .

There are library functions, typically from graphics packages, that take a large number of optional keyword arguments. Let’s call them “attributes”. If the user doesn’t specify them, such functions return convenient default results. This arrangement is very nice: the user typically needs to customize only a fraction of the attributes.

In this typical situation, how do you not give an attribute?

[In the following examples, func is not a wrapper to the library function. It just uses the library function as part of its job.]

# Method 1
function func(; atB = nothing, ...)
   ... do something ...
   if isnothing(atB)
     libraryfunc(;atA=..., atC=..., ...)
   else
     libraryfunc(;atA=..., atB=atB, atC=..., ...)
   end
end

# Method 2
function func(; atB = atB_default, ...)
   ... do something ...
   libraryfunc(; ..., atB=atB, ...)
end

# Method 3: NamedTuple
function func(; atB = (), ...)
   ... do something ...
   libraryfunc(; atB...,  ...) # splat atB.
end
func(; atA=..., atB = (atB = somevalue,), atC=...)
func(; atA=..., atC=...)

Method 1 quickly becomes impossible if your function has multiple optional attributes.

Method 2 requires that you know the default value. Sometimes there are obvious default values but at other times, you have to consult the documentation, or in the worst case, you have to look at the source code. Moreover, the default value may change in the future. So, a question is, is there a method to query the default value of an optional keyword argument?

Method 3 is the only reliable method I’ve found so far. The downsize is that it’s a bit tedious on the caller side: you need to create a NamedTuple. An upside is that you can bundle multiple attributes into a single NamedTuple: ats = (atB=..., atD=...), where you can include or omit arbitrary attributes to libraryfunc().

But, I wish all library functions regarded nothing as equivalent to not giving the argument. Or, can a macro be constructed in such a way that if the argument’s value is nothing, it be removed from the argument list?

Oh and, I should have added that Fortran as this nice optional qualifier to arguments:

subroutine func(atB)
   real, optional:: atB
   call libraryfunc(atB) ! knows whether atB is present or not.
end
call func()
call func(atB = somevalue)

The “presence” or “absence” propagates in Fortran and in libraryfunc, you can tell whether the argument is given or not.

If you know the set of possible keyword argument names accepted by libraryfunc, you can filter them out of the keyword arguments passed to func:

function func(; kwargs...)
    ... do something ...
    libraryfunc_kwargs = (`atA`, `atB`, `atC`)
    validkwargs = filter(kwargs) do (key, _)
        key in libraryfunc_kwargs
    end
    libraryfunc(; validkwargs...)
end

There you don’t need to know the default values, but still have to define the list of keys accepted by libraryfunc, in the variable libraryfunc_kwargs. Instead of writing them manually as in the example above, you can get them with Base.kwarg_decl, although if libraryfunc has several methods, you need to choose the one that you mean to call (easier if your function is type-stable).

This would become a bit more complicated if libraryfunc accepts variable keyword arguments (kwargs...). But in that case, probably you don’t even have to filter the ones passed to func.

1 Like

There is not, though I think nothing has exactly the meaning your want. It represents the knowledge that there is no value. Whether that is an appropriate default depends on the function in question though; in general, you can’t really get around “knowing” what the default value is if you implement something that depends on that value.

3 Likes

To do less keyword filtering, the caller function can reserve its kwargs... (or some more descriptive name like barplotkwargs...) to pass along to the library function call. Shared keywords like atA, atB, atC won’t need to be specified in the caller function header or library function call, and omitting any in the caller function call will let the library function use the default values. Although, if you wanted the caller function to have different defaults, you can specify those keywords, like atB = new_default, in the header and pass it by name to the library function call before the kwargs... (EDIT: as Salmon demonstrates in the next comment, kwargs... containing atA overrides the previous atA=1 instead of causing a repeated keyword syntax error).

If you have multiple library functions with different keyword argument sets, I think the neatest way would be partitioning into reserved NamedTuple/Dict arguments (could be positional or keyword) for the caller function, defaulting to empty ones.

A healthy library should standardize the keyword arguments, e.g. attributes for a kind of plot.

I’m not sure if that’s what you mean but if your function doesn’t explicitly depend on the argument you can just pass optional kwargs to the function.
Personally, I often do the following:

function func(a;kwargs...)
  [...]
  libraryfunc(a; atA=1, atC=...,kwargs...)
end

func(1;atB=:green) # passes :green to libraryfunc
func(1) # does not pass any attribute, libraryfunc will use its default
func(1;atA=5) # overwrites the default value atA=1
6 Likes

I would factor out the choice of defaults to a function, as in eg

default_atB() = ...

function func(; atB = default_atB(), ...)
    ...
end

You can even organize many into a single function in a package, eg

@inline defaults(s::Symbol) = defaults(Val(s))

defaults(::Val{:atB}) = ...

That said, IMO

is bad style. Organize them into structs or similar.

3 Likes

I usually do a mix – if I have a large number of keyword arguments and want to make it easy for the user to specify them (and not construct a struct themselves) I organise my function all in several levels

First level takes all keyword arguments and calls the struct constructors (these care for good defaults usually), then calls second level

Second level takes struct keywords and does the original work (one could call that the expert interface).

Is it always, though?
plotting packages such as CairoMakie.jl do this all the time, for example:

scatterlines(1:10;linewidth = 2, marker = '□',linestyle = :dash, colormap = :viridis, color = 1:10,...)

Sure, probably these kwargs ultimately end up in a struct somehow, but the user never interacts with them (and thankfully so, the style above is quite easy to use).

One point where I agree with you is that passing kwargs down the callstack makes it hard to see where they actually go or what kwargs are allowed in each function. But this is no issue if there is good documentation of the function.

1 Like

Thank you all for your inputs! I appreciate them.

Yes, I would do the same when that works. I said that func is not a wrapper to libraryfunc, by which I meant that func takes other keyword arguments that have nothing to do with libraryfunc. I should have been clearer.

I don’t think that that is always better than giving the user access to individual attributes. For example, I sometimes want to change only the line width:

plotgraph!(xs, ys; linewidth=3)

where plotgraph!() has a large number of attributes. I’m actually annoyed when such a function demands a struct or tuple

# lineattributes=(type, width, color, withsymbols)
plotgraph!(xs, ys; lineattributes=(:solid, 3, :auto, false))

You are giving more burden to the user. To avoid this burden, do you use NamedTuple?

plotgraph!(xs,ys; lineattributes=(linewidth = 3, ))

but of course, this isn’t better than plotgraph!(xs, ys; linewidth=3).

1 Like

Reading through the responses, I’ve gotten the impression that there is no “easy” and “obvious” solution for the user because there is no “easy” and “obvious” method for library writers.

I wish that the language (or a macro) provided a mechanism that removes the attribute from the list when its value is nothing or something equivalent or cleverer.

By design, nothing should be the universal signal of absence, but currently it’s not, which is likely because it’s tedious to always have to deal with nothing on the part of the library writers.

Actually, I was thinking of submitting to Makie a feature request that nothing be always treated as equivalent to absence, but then I immediately started to wonder why nothing isn’t already used like that.

If func’s keywords don’t need a kwargs..., then you can just reserve it for libraryfunc like how Salmon does it. If you can’t, you can pass libraryfunc’s keyword arguments in a NamedTuple in 1 argument of func’s call, defaulting to an empty NamedTuple(), and it’ll look just like putting parentheses around keyword arguments. Following Salmon’s example:

function func(a, libraryfunckwargs = NamedTuple())
  #=...          V these act like func-specific defaults=#
  libraryfunc(a; atA=1, atC=2, libraryfunckwargs...)
end

func(1, (atB=:green,)) # passes :green to libraryfunc
func(1) # does not pass any attribute, libraryfunc will use its default
func(1, (atA=5,)) # overwrites the default value atA=1

Seems simple enough.

That’s a different notion of “absence.” This is what its docstring says:

The singleton instance of type Nothing, used by convention when there is no value to return (as in a C void function) or when a variable or field holds no value.

atB = nothing is definitely not an absent keyword argument, so the instance nothing will normally override a default value. You’re effectively suggesting nothing to become a sentinel value for default values, which is very much not absent, and nothing was never designed to do that.

2 Likes

That’s what I meant by “Method 3” in my initial example and it’s what I currently use in my actual programs. I should have written my Method 3 as you show above.

You mix up two things:

  1. “What nothing is”, and
  2. “How a library function should treat it.”

You are correct that

atB = nothing is definitely not an absent keyword argument,

You describe “what” nothing is not.

My argument is that a library function should treat nothing as equivalent to the absence of the argument. I want to draw your attention to “by convention” in the sentence from the documentation you quote. How useful nothing is depends on “how” you use it.

nothing should be viewed as a signal that the variable “holds no value” (from the sentence from the documentation you quote). Then the real question is

What should a library function do if the optional keyword argument “holds no value”?

This is not a question about “what” nothing is. It is a question about what’s the best use of nothing. What should the library function do when the given variable “holds no value”?

Currently there are a lot of library functions that result in error if a given optional keyword argument is nothing. Is that the best design?

In many cases, treating nothing as if the optional argument were absent would be better than resulting in error.

To summarize, I think “we” should agree that “by convention”, an optional keyword argument should be regarded as absent if it’s given a nothing value. I think it agrees with “the spirit of nothing”.

When I manually assign a variable an instance, I expect that it isn’t reassigned automatically. Keyword arguments are not an exception in practice, and what happens with nothing arguments vary case by case. Sometimes it’s used to branch to different code with no default value to replace it, which is also a “spirit of nothing”, perhaps even more so. You could implement your desired default value behavior for your callers by filtering out nothing values from the NamedTuple of keyword arguments before passing into your library function, no need to instantiate defaults in callers.