[R] splitting up optional args

Jason Eisner
Sun, 11 Feb 2001 22:15:22 -0000

Hi,

A question (& possible suggestion) about function calls.

Is there an R idiom to eliminate the redundancy in the
following common situation?

  foo <- function(x, control=ComplicatedDefault) { etc. }

  plotfoo <- function(x, foocontrol=ComplicatedDefault, ...) {
               y <- foo(x, control=foocontrol)
               lines(x,y,...) }

The idea is that there are MANY optional parameters to plotfoo().
Any specified value for foocontrol should be passed to foo(), and the
other options should be passed to lines() via "...".

The main trouble above is that the same default for foo()'s control
parameter has to be specified in two places.  This makes it hard to
maintain the code correctly.

Further trouble with the above example becomes clear if we elaborate
it a bit:

  plotfoo <- function(x, foocontrol=ComplicatedDefault, ...) {
               y <- foo(x, control=foocontrol)
               z <- foo(x+1, control=foocontrol)
               lines(x,y,...)    # respect lines()'s defaults
               par(col=3)        # change lines()'s defaults
               lines(x,z,...)    # respect new defaults
               points(x,y+z,...) }  # respect points()'s defaults

When we call plotfoo(x), ComplicatedDefault is evaluated in plotfoo's
environment rather than foo's, and it is only evaluated once even
though we call foo twice.  This is not the same as if plotfoo simply
didn't specify a control parameter when calling foo.  It also
contrasts with the behavior of the unnamed optional arguments picked
up by "...".  For example, the default for the graphical parameter col
is evaluated in the environment of the callee (lines or points) and is
evaluated separately for each callee and on each call.

To eliminate the redundancy, I think one could write:

  plotfoo <- function(x, foocontrol=eval(formals(foo)$control), ...) {etc}

This is not terrible, but it is obscure for such a common situation,
and not mentioned in the documentation.  And it does not solve
the evaluation problems mentioned above.

Would it therefore make sense to extend R to allow the following?

  plotfoo <- function(x, foocontrol=, ...) {   # currently illegal
	y <- foo(x, control=foocontrol)
                plot(x,y,...) }

The rationale is that one can already call foo(x, control=) to request
the default control argument for foo.  Notionally, there is a special
"empty" value here that says "give me the default."  So it
would be
nice to be able to pass that empty value down the call stack.

The user presumably shouldn't be able to do much with this empty value
except to pass it as a parameter.  Indeed the empty value is
appropriately analogous to "..." in that it has such restricted use.

I do recognize that since the user can inspect (and construct?) code,
an expression for the empty value would be accessible as
formals(plotfoo)$foocontrol or as substitute(foocontrol) evaluated
within plotfoo(x).

But this expression could be unevaluable, just as formals(plotfoo)$x
is unevaluable.  (Of course these two unevaluable expressions should
be different, so that the user of formals() can see that x is a
required argument and foocontrol isn't.)

An alternative is to make this expression evaluable.  Suppose we say
that it evaluates to a special object DEFAULT.  Then presumably
there would be no way to distinguish between
   foo(x, control=DEFAULT)
   foo(x, control=)
   foo(x)
but that seems okay to me.  An advantage to this approach is that
the user could write potentially useful expressions like
   foo(x, control=ifelse(x==0, 3, DEFAULT))
and plotfoo could perhaps test its argument to find out whether
it was DEFAULT, i.e., not specified by the user.

By the way, another use for the proposed extension is as follows:
  plotfoo <- function(x, col=, lty=) { lines(x,foo(x),col=col,lty=lty) }
This is simply a version of
  plotfoo <- function(x, ...) { lines(x,foo(x), ...) }
that restricts the caller to certain named parameters, for safety.

I am an R novice and have undoubtedly have missed some considerations
here, so would be interested in any response from the experts.  Thanks!