Skip to content
Prev 6760 / 63421 Next

Mishandling missing "..." (PR#1247)

Thomas Lumley wrote:
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:
Error: SubAssignArgs: invalid number of arguments

OTOH,
[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:
[1] 0 0 0 0 0 0 0 0 0 0
[1]  1  2  3  4  5  6  7  8  9 10
[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