[R] splitting up optional args

jason@cs.rochester.edu
Mon, 12 Feb 2001 03:24:01 -0000

Brian: Thanks for your clear and useful response.  The NULL trick is
essentially how it's done in Common Lisp (with nil instead of NULL).
And the missing() trick is even better because it makes it possible to
write generic functions that treat NULL as they would any other value.

I certainly don't question your experience that

> I have yet to see an example in which these did not suffice, and
> I've been doing this for more than a decade.

So the rest of this note is perhaps academic.  But so are we. :-)

My question is now entirely about elegance, not power.  Doesn't your
answer amount to discouraging people from using the variable=value
default syntax at all?  That syntax is part of R for a reason: it
makes code easier to write, read, and interrogate (via args()) than if
explicit tests for missingness are used.  But if the author of foo()
chooses to use that clean syntax, then she is apparently making it
impossible for outside callers of foo() to inherit its default (except
when "..." suffices).  I don't see what the author of fooplot()
can do
in this case, short of modifying the source of foo() every time a new
version comes out.

This seems like an unhealthy tension between modular code and readable
code, so I'm still wondering if it can be resolved.

My suggestions were about automatic binding of defaults, whereas you
suggest explicit binding.  But your answer makes it clear that my
suggestion is already almost entirely implemented in R!  There is
already a special unevaluable expression (unforceable promise) for a
missing parameter and this can already be passed down the call stack.
So my suggested
   plotfoo <- function(x, foocontrol=) { foo(x, control=foocontrol)}
is in fact equivalent to the existing
   plotfoo <- function(x, foocontrol) { foo(x, control=foocontrol)}
since (to my surprise) it's legal to omit parameters without defaults.

Indeed, the only sticking point is that when a function is called, it
doesn't automatically substitute defaults for all variables with
missing() values, only for those that were not supplied by the actual
call.  This is why, as you say, "you have to use an explicit test for
missingness."

Could this be changed?  It seems to require a mere check for top-level
missingness of actual arguments when binding them to formal arguments
that have defaults.  Nothing needs to be evaluated.  Are there strong
reasons against doing this (speed, compatibility, something else)?

Again, I'm making some guesses about how things work here, by
analogy from other languages, so I may be full of holes.

Thanks again - you evidently spend a great deal of time helping people
on this list!

> Also, missingness is passed down in R but not S, so
>
> foo <- function(x, control)
> {
>    if(missing(control)) cat("using default control\n")
> }
> plotfoo <- function(x, foocontrol) { foo(x, control=foocontrol)}<
>
> > plotfoo(1:10)
> using default control
>
> does work, although you have to use an explicit test for missingness.
>
> The more common version is
>
> foo <- function(x, control=NULL)
> {
>    if(is.null(control)) cat("using default control\n")
> }
> plotfoo <- function(x, foocontrol=NULL) { foo(x, control=foocontrol)}