[Rd] Mishandling missing "..." (PR#1247)

John Chambers jmc@research.bell-labs.com
Mon, 07 Jan 2002 18:06:00 -0500


Thomas Lumley wrote:
> 
> On Mon, 7 Jan 2002 brahm@alum.mit.edu wrote:
> 
> > R> myfun <- function(x, ...) {x[...] <- 0; x}
> > R> myfun(3)
> >    Error in myfun(3) : SubAssignArgs: invalid number of arguments
> >
> > It fails because no ... was passed.  The workaround (and desired behavior) is:
> > R> myfun <- function(x, ...) {if (missing(...)) x[] <- 0 else x[...] <- 0; x}
> > R> myfun(3)
> >    [1] 0
> >
> 
> This is interesting.  Another workaround is
> 
> > myfun
> function(x, ...) {x[...] <- 0; x}
> > myfun(3,)
> [1] 0
> 
> and the problem goes down to eval.c:evalListKeepMissing, which says
>         /* If we have a ... symbol, we look to see what it is bound to.
>          * If its binding is Null (i.e. zero length)
>          *      we just ignore it and return the cdr with all its
>                expressions evaluated;
> 
> The difference between  myfun(3) and myfun(3,) is that in the former case
> there are no ... arguments, in the latter there is a ... argument but it's
> missing.
> 
> In one way this would be fairly easy to fix by writing yet another variant
> of evalList that replaced an empty ... with R_MissingArg, but I'm scared.
> 
>         -thomas


My feeling is that it's the ordinary behavior of x[] <- 0 that is
puzzling.  Even though the example with myFun generates an error, it
looks more consistent with a general semantics than does the "raw"
expression.

Shouldn't the expression x[] <- 0 be  equivalent to "[<-(x, 0), just as

  x[i]<-0; x[i,j] <- 0

are equivalent to

  "[<-"(x,i,0); "[<-"(x,i,j,0)
??

But in fact the functional form of the operator generates the same
error:

> x <- 1:10
> "[<-"(x, 0)
Error: SubAssignArgs: invalid number of arguments

OTOH,

> "[<-"(x, , 0)
 [1] 0 0 0 0 0 0 0 0 0 0

The internal problem seems to be that SubAssignArgs is supposed to
return a subscript list of length 1 containing one element which is
"missing", rather than a list of length 0 for the x[] <-... case.  If it
could return the latter, and if the switch statement in
do_subassign_dflt didn't ignore the case of 0 subscripts, but had
instead
	case 0:
	   x = VectorAssign(call, x, R_MissingArg, y);
	   break;

then the anomalies seem to go away.  Making this change in the r-devel
code, and returning something from SubAssignArgs for the case nsub==0
gives:

> myfun <- function(x, ...) {x[...] <- 0; x}
> x <- 1:10
> myfun(x)
 [1] 0 0 0 0 0 0 0 0 0 0
> x
 [1]  1  2  3  4  5  6  7  8  9 10
> x[] <- 0
> x
 [1] 0 0 0 0 0 0 0 0 0 0


Not to beat longer on the question than it perhaps deserves, but the
"workaround"  using missing(...) seems wrong in principle.  Requires a
digression on the history of "..."; sorry about that.

The essential meaning of "..."  is that, if it's a formal argument to a
function, f, and in the body of f there is a call, say, g(...), then the
effect is equivalent to calling g with all the arguments that match
"..." (with arguments evaluated in the right environment of course).

This has always been a rather dicey bit of the language, since it is
much less object-based than any other concept in the basic semantics. 
In trying to elaborate the model somewhat, we came up with the notion
that "..." was roughly like having objects ..1, ..2, etc. in the local
frame, up to n where n == length(list(...)).   The evaluator then
roughly turns g(...) into g(<name1>=..1, ....) with the appropriate
names taken from the call to f.

As a semantic model, this is not great, but the mechanism is pretty
clearly useful in practice.  (And I have to say that none of the
versions I know of the same mechanism in other languages, developed in
parallel with our floundering around, impress me as better in terms of
combined usefulness and clarity.)

But it leads to some tricky situations if one interprets "..." as an
ordinary variable.

For example, missing(...) doesn't work if interpreted according to the
semantics for "...".  For example:

  f <- function(x, ...) { if(missing(...)) x else ## something or
other..}

f(1)

should be equivalent to { if(missing())... 

f(1,2, 3)

should be equivalent to { if(missing(..1, ..2))....

neither of which should be legal.  

So, missing(...) seems a questionable expression in any context.

John


-- 
John M. Chambers                  jmc@bell-labs.com
Bell Labs, Lucent Technologies    office: (908)582-2681
700 Mountain Avenue, Room 2C-282  fax:    (908)582-3340
Murray Hill, NJ  07974            web: http://www.cs.bell-labs.com/~jmc
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-devel 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-devel-request@stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._