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 _._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._