如何为未评估的赋值表达式定义 S3 方法?

How can I define an S3 method for an unevaluated assignment expression?

tl;dr: R CMD check complains when I implement a generic for the S3 class <-, because it thinks that the function is a replacement function with incorrect arguments.

我需要定义一组 S3 泛型来遍历未计算的 R 表达式的 AST。

出于演示目的,请考虑以下 S3 泛型及其方法:

walk = function (x) UseMethod('walk')

walk.default = function (x) message('default')

walk.name = function (x) message('name')

walk.call = function (x) message('call')

这很好用:

tests = alist('2', c, f(1))

invisible(lapply(tests, walk))
default
name
call

但是,有不少调用表达式的S3class不是call;例如:

tests2 = alist(for (x in y) ., if (.) ., x <- .)
invisible(lapply(tests2, walk))
default
default
default

……哎呀。我希望 将这些表达式视为调用。我可以通过添加更多方法来做到这一点:

walk.for = function (x) message('for')

walk.if = function (x) message('if')

`walk.<-` = function (x) message('<-')

# … and so on for other syntax constructs.

现在我在调用 walk 时得到了预期的结果:

for
if
<-

然而,这段代码是一个包的一部分,R CMD check抱怨walk.<-的定义,因为它认为该函数是一个替换函数:

W  checking replacement functions ...
    ‘walk.<-’
  The argument of a replacement function which corresponds to the right
  hand side must be named ‘value’.

而且我明白 为什么 我会收到警告(事实上,这看起来像是为 walk. 定义替换函数的拙劣尝试)。但是当然这个 不是 替换函数,所以警告是误报。那么我应该如何明确地为 class <- 实现 S3 泛型呢?或者这是 R CMD check 中的错误?我可以摆脱警告吗?我无法向函数添加额外的参数。

Writing R Extensions, section 1.5.2 Registering S3 methods提供了一个变通方案:

It is possible to specify a third argument to S3method, the function to be used as the method, for example

S3method(print, check_so_symbols, .print.via.format)

when print.check_so_symbols is not needed.

这意味着,我们不定义 `walk.<-`,而是执行以下操作:

R/‹some-file›.r 中的实施:

walk_assign = function (x) message('<-')

(与其他 S3 方法不同,函数名称并不重要。)

NAMESPACE声明:

S3method(walk, '<-', walk_assign)

这与在常规 R 脚本中调用 .S3method('walk', '<-', \(x) message('<-')) 的效果相同。有趣的是,.S3method() also 似乎在包内工作(而不是上面基于 NAMESPACE 的解决方案),但函数的文档说不要在包中使用它所以这可能会在未来崩溃。


当然,正如 Allan Cameron 评论的那样,一个可以说更简单的解决方案是将 <- 案例与 default 融合,并在 walk.default 中使用 if 来消除歧义:

walk.default = function (x) {
    if (inherits(x, '<-')) {
        message('<-')
    } else {
        message('default')
    }

}

但是,我个人不喜欢将 S3 调度与 if 混合使用。