注意:在提到文档和源代码时,我提供了链接到 R 的 官方 Subversion 仓库的一个 非官方 GitHub 镜像。这些链接绑定到 GitHub 仓库中的 提交记录 97b6424,该提交记录对应于 Subversion 仓库中的 修订版本号 81461
(此编辑时为最新版本)。
substitute
是一个“特殊”的函数,其参数不会被评估(doc)。
typeof(substitute)
[1] "special"
这意味着substitute
的返回值可能与解析器逻辑不一致,具体取决于内部如何处理未评估的参数。
通常情况下,substitute
将调用...(<exprs>)
作为形式为pairlist(R_DotsSymbol, <exprs>)
(doc)的LANGSXP
。 substitute
调用的上下文决定了如何处理SYMSXP
R_DotsSymbol
。特别地,如果在带有...
作为正式参数和rho
作为其执行环境的函数内部调用了substitute
,则结果为:
findVarInFrame3(rho, R_DotsSymbol, TRUE)
在C语言实用程序的主体中,
substituteList
(
source) 是
DOTSXP
或
R_MissingArg
,仅当
f
没有参数调用时才是后者(
doc)。在其他情况下,结果是
R_UnboundValue
或(特殊情况下)一些其他
SEXP
,仅当在
rho
中将一个值绑定到名称
...
时才是后者。
substituteList
会特殊处理这些情况。
R_DotsSymbol
的处理方式不同造成了以下R语句产生不同的结果的原因。
f0 <- function() substitute(...(n = 1)); f0()
f1 <- function(...) substitute(...(n = 1)); f1()
g0 <- function() {... <- quote(x); substitute(...(n = 1))}; g0()
g1 <- function(...) {... <- quote(x); substitute(...(n = 1))}; g1()
h0 <- function() {... <- NULL; substitute(...(n = 1))}; h0()
h1 <- function(...) {... <- NULL; substitute(...(n = 1))}; h1()
考虑到...(n = 1)
的解析方式,您可能期望f1
返回call("...", n = 1)
,g0
和g1
都返回call("x", n = 1)
,h0
和h1
都会抛出错误,但由于上述原因大多数未记录,情况并非如此。
内部机制
在R函数f
内部调用时,
f <- function(...) substitute(...(<exprs>))
substitute
函数调用C语言工具do_substitute
,你可以通过查看here来了解这一点。在该函数中,argList
得到一个LISTSXP
,其形式为pairlist(x, R_MissingArg)
,其中x
是一个LANGSXP
,其形式为pairlist(R_DotsSymbol, <exprs>)
(source)。
如果您跟随do_substitute
的主体,那么您将发现从do_substitute
传递给substituteList
的t
的值是一个LISTSXP
,其形式为pairlist(copy_of_x)
(source)。
由此可知,在substituteList
调用的while
循环( source )中只有一个迭代,并且循环体中的语句CAR(el) == R_DotsSymbol
( source )在该迭代中为false
。
在条件语句的
false
分支中 (
source),
h
得到了值
pairlist(substituteList(copy_of_x, env))
。循环退出,
substituteList
将
h
返回给
do_substitute
,然后将
CAR(h)
返回给 R (源
1,
2,
3)。
因此,
substitute
的返回值为
substituteList(copy_of_x, env)
,现在需要推导出这个
SEXP
的身份。在
substituteList
中,
while
循环有
1+m
次迭代,其中
m
是
<exprs>
的数量。在第一次迭代中,循环体内的语句
CAR(el) == R_DotsSymbol
为
true
。
在条件语句的
true
分支中 (
source),
h
要么是
DOTSXP
,要么是
R_MissingArg
,因为
f
将
...
作为形式参数 (
doc)。继续查看,您会发现
substituteList
的返回值为:
- 如果在第一个
while
迭代中h
是R_MissingArg
,并且m = 0
,则返回R_NilValue
。
否则,返回一个LISTSXP
,其中列出了h
中的表达式(如果h
在第一个while
迭代中为DOTSXP
),后跟<exprs>
(如果m > 1
),所有表达式均未求值且没有替换,因为在substitute
调用时,f
的执行环境为空。
确实:
f <- function(...) substitute(...())
is.null(f())
f <- function(...) substitute(...(n = 1))
identical(f(a = sin(x), b = zzz), pairlist(a = quote(sin(x)), b = quote(zzz), n = 1))
杂项
顺便提一下,为了帮助我更好地理解,我在coerce.c
中添加了一些打印语句,并重新编译了R。例如,在do_substitute
(source)函数体中的UNPROTECT(3);
之前,我添加了以下内容:
Rprintf("CAR(t) == R_DotsSymbol? %d\n",
CAR(t) == R_DotsSymbol);
if (TYPEOF(CAR(t)) == LISTSXP || TYPEOF(CAR(t)) == LANGSXP) {
Rprintf("TYPEOF(CAR(t)) = %s, length(CAR(t)) = %d\n",
type2char(TYPEOF(CAR(t))), length(CAR(t)));
Rprintf("CAR(CAR(t)) = R_DotsSymbol? %d\n",
CAR(CAR(t)) == R_DotsSymbol);
Rprintf("TYPEOF(CDR(CAR(t))) = %s, length(CDR(CAR(t))) = %d\n",
type2char(TYPEOF(CDR(CAR(t)))), length(CDR(CAR(t))));
}
if (TYPEOF(s) == LISTSXP || TYPEOF(s) == LANGSXP) {
Rprintf("TYPEOF(s) = %s, length(s) = %d\n",
type2char(TYPEOF(s)), length(s));
Rprintf("TYPEOF(CAR(s)) = %s, length(CAR(s)) = %d\n",
type2char(TYPEOF(CAR(s))), length(CAR(s)));
}
这帮助我确认了前一行substituteList
调用中输入和输出的内容:
f <- function(...) substitute(...(n = 1))
invisible(f(hello, world, hello(world)))
CAR(t) == R_DotsSymbol? 0
TYPEOF(CAR(t)) = language, length(CAR(t)) = 2
CAR(CAR(t)) = R_DotsSymbol? 1
TYPEOF(CDR(CAR(t))) = pairlist, length(CDR(CAR(t))) = 1
TYPEOF(s) = pairlist, length(s) = 1
TYPEOF(CAR(s)) = pairlist, length(CAR(s)) = 4
invisible(substitute(...()))
CAR(t) == R_DotsSymbol? 0
TYPEOF(CAR(t)) = language, length(CAR(t)) = 1
CAR(CAR(t)) = R_DotsSymbol? 1
TYPEOF(CDR(CAR(t))) = NULL, length(CDR(CAR(t))) = 0
TYPEOF(s) = pairlist, length(s) = 1
TYPEOF(CAR(s)) = language, length(CAR(s)) = 1
显然,使用调试符号编译R并在调试器下运行R也有帮助。
另一个谜题
刚刚注意到这个奇怪的现象:
g <- function(...) substitute(...(n = 1), new.env())
gab <- g(a = sin(x), b = zzz)
typeof(gab)
gab
有人可以进行深入研究,找出为什么当你提供env
参数时(包括env = NULL
),结果是LANGSXP
而不是LISTSXP
吗?请注意保留HTML标签。
substitute(...())
。它没有得到真正的回答,是吗? - Henrik(function(...) substitute(...()))(a,b)
在版本1.3.1(2001年9月)和1.4.1(2002年1月)之间添加。在1.4.1中,它可以正常运行。已经过去20年了,但仍未被记录(?) - Donald Seinen?substitute
确实会发出警告:“不能保证生成的表达式有任何意义。”我猜这是为了捕捉奇怪行为而设计的。 - Mikael Jagan