Next: , Previous: Evaluation of expression objects, Up: Computing on the language



6.5 函数调用的操作

可以通过查看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)不能使用; 这是一个可以论证的程序问题。

为创建函数调用,还有两个额外的函数可以使用,它们分别是 calldo.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函数。