This package was developed due to behavior of match.call
in some very specific
corner cases:
...
in their formals...
This issue cropped up when trying to match calls that happened earlier in the
dynamic call stack to the call to match.call
(e.g. the grand-parent call).
fun0 <- function(...) { fun_gpar <- function(b, ...) { fun_par <- function(a) match.call(fun_gpar, sys.call(sys.parent()), expand.dots=FALSE) fun_par() } fun_gpar(...) } fun0(999, 2 + 2, 3 * pi(), 4)
Notice how:
..X
instead of their expressionsb
(this isn't obvious from the above,
but it is mismatched).Also, note that sys.call(sys.parent())
is not the same as the default value for
argument call
of match.call
even though it looks like it is. The difference
is in the example above, the expression is evaluated in the parent context,
whereas the default call
value is evaluated within match.call
's context.
The reason match.call
doesn't work properly in the aforementioned circumstances
is that match.call
always uses the lexical stack of the function that invokes
match.call
to substitute the ...
argument, irrespective of where the call
you are attempting to match is issued. In our simple example, match.call
will
be matching the dots in the lexical parent of fun_par
, instead of in the
calling frame of fun_gpar
. In this case, match.call
is matching to the
dots in fun_gpar <- function(b, ...) {
on the second line instead of the dots
in fun_gpar(...)
on the 7th line. These look similar, but are really
completely different.
This mismatch causes the problems described above. Since fun_gpar
has a
formal argument b
in addition to ...
, when we invoke fun_gpar(...)
the
actual ...
argument inside fun_gpar
will be the original ...
argument
passed to fun_gpar
, less the explicit formal arguments that are matched.
In this case, b
positionally matches the 999, so the ...
inside
fun_gpar
no longer contains 999, but match.call
uses this 999-less ...
to
match the fun_gpar(...)
call, not the original ...
in the dynamic parent
frame parent of fun_gpar
.
This behavior is not unreasonable since match.call
doesn't provide any way to
give it the correct frame for substituting the dots in.
In addition, when substituting dots, R will replace any non-constant values with
..X
, etc. Not entirely clear why this happens, but it is done in this following
snippet of code in subDots
at lines 1258-1268 in src/main/unique.c
(R 3.0.2):
for(a = dots, b = rval, i = 1; i <= len; a = CDR(a), b = CDR(b), i++) { snprintf(tbuf, 10, "..%d",i); SET_TAG(b, TAG(a)); t = CAR(a); while (TYPEOF(t) == PROMSXP) t = PREXPR(t); if( isSymbol(t) || isLanguage(t) ) SETCAR(b, mkSYMSXP(mkChar(tbuf), R_UnboundValue)); // <--- HERE else SETCAR(b, t); }
Here is a comprehensive list of the scenarios that we have found to cause
problems with match.call
:
fun_gpar
must be a LEXICAL parent of fun_par
otherwise we get error
"... used in a situation where it does not exist"; this demonstrates that
match.call
descends through the lexical stack, not the dynamic stack.fun_par
has a ...
in its formal definition, then none of the
...
arguments of fun_gpar
are captured if the call to fun_gpar
doesn't also include the ...
arguments. This is because we redefine
...
to be the arguments to fun_par
, but then our call to fun_par
doesn't have any arguments, so the lexically earliest set of dots is empty.fun_par
doesn't have any formals, then match.call
will grab
the ...
values, but with limitations:
a. unnamed arguments in ...
will be consumed by the named formals of
fun_gpar
to the extent those are not otherwise matched, but
these consumed arguments will not show up in the output of match.call
b. If the call to fun_gpar
involves expressions or symbols, these are
replaced with ..1
, ..2
, etc. instead of being captured properlySee below for actual code examples
fun1 <- function(a, ...) fun_gpar(a, ...) fun_gpar <- function(b, ...) fun_par() fun_par <- function() match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F) fun1(3, "test", x=45, zest="lemon")
fun2 <- function(a, ...) { fun_gpar <- function(b, ...) { fun_par <- function(...) match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F) fun_par() } fun_gpar(a, ...) } fun2(3, "test", x=45, zest="lemon")
fun3 <- function(a, ...) { fun_gpar <- function(b, c, d, ...) { fun_par <- function() match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F) fun_par() } fun_gpar(a, ...) } fun3(3, "test", 59, x=45, zest="lemon", 58)
We lost "test" and "59", because ...
was reduced by the formals otherwise
not matched (b
was directly matched by a
), and after reduction, it was
used to fill the call.
fun3(3, "test", 59, x=45, zest="lemon", (58), (60))
Expressions are returned as their position in dots, since we lost the first two elements we're left with third and fourth.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.