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!
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
Send "info", "help", or "[un]subscribe"
(in the "body", not the subject !) To: r-help-request at
stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._
If you look at some examples, I think that you will see that quite often
NULL is used to indicate your DEFAULT.
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)}
I have yet to see an example in which these did not suffice, and I've been
doing this for more than a decade.
On Sun, 11 Feb 2001, Jason Eisner wrote:
> 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!
>
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
> r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
> Send "info", "help", or "[un]subscribe"
> (in the "body", not the subject !) To: r-help-request at
stat.math.ethz.ch
>
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._
>
--
Brian D. Ripley, ripley at stats.ox.ac.uk
Professor of Applied Statistics, http://www.stats.ox.ac.uk/~ripley/
University of Oxford, Tel: +44 1865 272861 (self)
1 South Parks Road, +44 1865 272860 (secr)
Oxford OX1 3TG, UK Fax: +44 1865 272595
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
Send "info", "help", or "[un]subscribe"
(in the "body", not the subject !) To: r-help-request at
stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._
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)}-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html Send "info", "help", or "[un]subscribe" (in the "body", not the subject !) To: r-help-request at stat.math.ethz.ch _._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._