请解释为什么会/不会发生以下错误:
ff = factor(1:3)
`[.factor`(ff) # okay
base::`[.factor`(ff) # Error in NextMethod("[") : 'NextMethod' called from an anonymous function
f = `[.factor`; f(ff) # Error in NextMethod("[") : wrong value for .Method
请解释为什么会/不会发生以下错误:
ff = factor(1:3)
`[.factor`(ff) # okay
base::`[.factor`(ff) # Error in NextMethod("[") : 'NextMethod' called from an anonymous function
f = `[.factor`; f(ff) # Error in NextMethod("[") : wrong value for .Method
这是一个令人惊讶的微妙而复杂的问题.
之所以base::`[.factor`(ff)
不能工作,而`[.factor`(ff)`
能工作,即使这两段代码从字面上调用the same function,是因为NextMethod
的工作方式不寻常.TL;DR是`[.factor`
是S symbol,而base::`[.factor`
是call.
当你调用NextMethod
时,underlying C code会计算出它被调用的是context,如果不满足几个条件中的任何一个,它就会抛出一个错误.
其中一个条件是NextMethod
所在的函数必须被直接调用--从技术上讲,调用中的第一个 node 必须是symbol,而不是表达式.如果您试图通过调用获取函数,NextMethod
会检测到这一点,并给出"匿名函数"错误.
NextMethod
还会判断函数的名称是否与正在使用的泛型一致,如果不匹配,我们会得到"错误的.Method
的值"错误
我们可以用一个简单的例子来演示相同的行为.我们有一个"Foo"类的对象:
foo <- structure(1:3, class = c("foo"))
foo
#> [1] 1 2 3
#> attr(,"class")
#> [1] "foo"
我们希望能够设置"foo"的子集,以便"foo"的任何子集仍然属于"foo"类,但除此之外,我们希望使用与数字向量完全相同的子集设置规则.这就是NextMethod
的用武之地:
`[.foo` <- function(obj, ind) {
y <- NextMethod("[")
class(y) <- "foo"
y
}
foo[2]
#> [1] 2
#> attr(,"class")
#> [1] "foo"
它的工作方式与预期一致,直接调用它也是如此:
`[.foo`(foo, 2)
#> [1] 2
如果我们试图调用函数indirectly,比如通过get
,那么NextMethod
会抛出错误,因为get("[.foo")
是一个call,而不是一个符号.
get("[.foo")(foo, 2)
#> Error in NextMethod("[") : 'NextMethod' called from an anonymous function
即使将调用包装在圆括号或花括号中也足以生成错误(因为这些括号实际上也是调用)
(`[.foo`)(foo, 2)
#> Error in NextMethod("[") : 'NextMethod' called from an anonymous function
{`[.foo`}(foo, 2)
#> Error in NextMethod("[") : 'NextMethod' called from an anonymous function
如果我们try 重命名该函数,得到的错误与您try 重命名[.factor
时遇到的错误相同:
f <- `[.foo`
f(foo, 2)
#> Error in NextMethod("[") : wrong value for .Method
现在f
至少是symbol,但它是wrong符号(f
不是[
)
最后一个谜题是为什么base::`[.factor`
不是一个符号.这是因为在R中命名空间符号::
实际上是call,所以base::`[.factor`
实际上被解析为`::`(base, `[.factor`)
class(quote(base::`[.factor`))
#> [1] "call"
然而,没有命名空间标识符,我们得到的是symbol(也就是"姓名")
class(quote(`[.factor`))
#> [1] "name"