Next: Manipulation of functions, Previous: Evaluation of expression objects, Up: Computing on the language
可以通过查看sys.call的结果
来了解一个函数是如何被调用的。
下面是一个具体的例子,简单展示了一个函数的调用情况:
> f <- function(x, y, ...) sys.call()
> f(y = 1, 2, z = 3, 4)
f(y = 1, 2, z = 3, 4)
但是,除了调试,这不总是有用的,因为它需要函数跟踪参数匹配以
解释函数调用情况。例如,在上面的例子中,它必须
可以发现x 的第二个事实参数被第一个形式参数匹配。
通常,我们需要所有事实参数和对应的形式参数绑定的调用。为
达到这个目的,可以采用 match.call。
这里是前述例子的一个变种,就是一个通过参数匹配返回
它自身调用的函数
> f <- function(x, y, ...) match.call()
> f(y = 1, 2, z = 3, 4)
f(x = 2, y = 1, z = 3, 4)
注意第二个参数现在和x匹配,并且
在结果出现在对应的位置中。
该项技术的最初使用是通过一样的参数调用另外一个函数,
但可能会删除或增加一些其它的参数。一个典型的应用可以参见
lm 函数代码的起始部分:
mf <- cl <- match.call()
mf$singular.ok <- mf$model <- mf$method <- NULL
mf$x <- mf$y <- mf$qr <- mf$contrasts <- NULL
mf$drop.unused.levels <- TRUE
mf[[1]] <- as.name("model.frame")
mf <- eval(mf, sys.frame(sys.parent()))
注意结果调用在父框架下被求值。
在该框架下可以确定相关的表达式是有意义的。
该调用可以看作是一个列表对象,它的第一个元素是函数名字,其它元素
是以形式参数名字作为标签的事实参数表达式。
因此,去除不想要的参数的技术可用来分配
NULL,如第2和第3行所示,和增加一个我们用标签列表赋值的参数(
这里传递drop.unused.levels = TRUE),如第4行所示。
为了改变被调用的函数的名字,既可以用这里的as.name("model.frame")构造
也可以用quote(model.frame) 给列表的第一个元素赋值并且确信
该值就是名字。
match.call函数有一个expand.dots的参数,它是一个开关,如果设为
FALSE将会使得所有... 参数
成为标签为 ... 的单个参数。
> f <- function(x, y, ...) match.call(expand.dots = FALSE)
> f(y = 1, 2, z = 3, 4)
f(x = 2, y = 1, ... = list(z = 3, 4))
... 参数是一个列表(准确地说是成对列表),
它和 S里面调用 list
不一样:
> e1 <- f(y = 1, 2, z = 3, 4)$...
> e1
$z
[1] 3
[[2]]
[1] 4
使用这种形式的 match.call 的原因是简单地摆脱任何
... 参数不至于传递一些函数未知的没有详细说明的
参数。下面是一个来自 plot.formula 的例子:
m <- match.call(expand.dots = FALSE)
m$... <- NULL
m[[1]] <- "model.frame"
一个更加精细的应用是函数 update.default。可以在该函数中的
可选额外参数集合中增加,替换,或取消一些原始调用的参数:
extras <- match.call(expand.dots = FALSE)$...
if (length(extras) > 0) {
existing <- !is.na(match(names(extras), names(call)))
for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
if (any(!existing)) {
call <- c(as.list(call), extras[!existing])
call <- as.call(call)
}
}
注意,一旦 extras[[a]] == NULL,
单个修改已经存在的参数需要小心一点。
如前所示,在没有强迫的情况下调用一个对象时,
连接操作(concatenation)不能使用;
这是一个可以论证的程序问题。
为创建函数调用,还有两个额外的函数可以使用,它们分别是
call 和 do.call。
函数 call 允许通过函数名字和参数列表
创建一个调用对象
> x <- 10.5
> call("round", x)
round(10.5)
如上所见, 是x的值而不是符号
加入了调用中,因此和 round(x)有明显的差异。
这种形式用的非常地少,但是当一个函数的名字可以作为
一个字符变量时,这会非常有用。
函数 do.call 是相关的,但会立即对调用求值和
从含有所有参数的模式为"list"的对象里面获取参数。
一个很自然的应用是当我们向把一个函数(如cbind)
用于一个列表或数据框的所有对象时。
is.na.data.frame <- function (x) {
y <- do.call("cbind", lapply(x, "is.na"))
rownames(y) <- row.names(x)
y
}
其它一些应用包括基于 do.call("f",
list(...)) 构造的变种。但是,我们必须知道这包括实际参数调用
前的参数求值。这可能阻止函数自身的悠闲求值和参数替换方面。
一个类似的注意同样适用于call函数。