仔细阅读?setMethod
本书,就能理解这些问题中的许多.我将逐一阐述您的建议,然后添加一些我自己的建议.
1)
setMethod("[", signature = c(x = "Foo"),
function(x, ...) {
print(list(...))
x@vec[...]
})
该方法的形参与泛型函数的形参不匹配.setMethod
会自动更正它们,而不会发出警告(叹息).因此,您的actual方法如下所示:
getMethod("[", signature = c(x = "Foo"))
Method Definition:
function (x, i, j, ..., drop = TRUE)
{
print(list(...))
x@vec[...]
}
Signatures:
x
target "Foo"
defined "Foo"
当我们调用foo[2]
时,我们发现x
与foo
匹配,i
与2
匹配,...
与零匹配.因此,list(...)
为空,x@vec[...]
的计算结果为x@vec
.
2)
setMethod("[", signature = c(x = "Foo"),
function(x, i, j, ..., drop = TRUE) {
x@vec[i, j, drop = drop]
})
此方法的形参很好,但您将向量作为数组进行索引,这是错误的.您可以忽略除i
之外的所有内容,只返回x@vec[i]
,但仍然存在trap (例如,可能缺少i
).
3)
setMethod("[", signature = c(x = "Foo"),
function(x, ...) {
call <- as.list(match.call())
fun <- call[[1]]
str(fun)
args <- call[-1]
str(args)
do.call(fun, args)
})
这个是错误的,因为(同样)形式参数与泛型函数的形参不匹配,并且正如您所指出的那样:fun
是一个符号,args
是语言对象(可能还有常量)的列表,这不是do.call
所期望的.无论如何,我的观点是,基于match.call
的方法过于复杂和脆弱.
我会怎么做
您对类Foo
的定义意味着foo@vec
没有dim
属性.它始终是数值向量,而不是array.
Foo <- setClass("Foo", slots = c(vec = "numeric"))
foo.v <- Foo(vec = c(1, 2, 3))
foo.a <- Foo(vec = toeplitz(1:6))
## Error in validObject(.Object) :
## invalid class "Foo" object: invalid object for slot "vec" in class "Foo": got class "matrix", should be or extend class "numeric"
对于这个类似向量的类Foo
,我将定义:
setMethod("[", signature = c(x = "Foo", i = "ANY", j = "missing", drop = "missing"),
function(x, i, j, ..., drop = TRUE) {
if (nargs() > 2L)
stop("'x' of class Foo must be indexed as x[i]")
else if (missing(i))
x@vec
else x@vec[i]
})
这允许x[]
或x[i]
,同时禁止x[drop=]
、x[i, ]
、x[i, , drop=]
、x[i, j]
和x[i, j, drop=]
中的所有,这对于类向量类没有意义.
foo.v[]
## [1] 1 2 3
foo.v[-2L]
## [1] 1 3
foo.v[1L, 1L, drop = FALSE]
## Error in foo.v[1L, 1L, drop = FALSE] : object of type 'S4' is not subsettable
这个错误有点不透明.之所以出现这种情况,是因为我们的方法没有被调度用于调用中的签名.在实践中,您可以通过其他方法引发更透明的错误来捕获此类情况.
现在让我们考虑与Foo
类似的第二个类Bar
,但其vec
位可以是数字向量或任何array.
setClassUnion("vectorOrArray", c("numeric", "array"))
Bar <- setClass("Bar", slots = c(vec = "vectorOrArray"))
bar.v <- Bar(vec = c(1, 2, 3))
bar.a <- Bar(vec = toeplitz(1:6))
对于这个向量或类似数组的类Bar
,我将定义:
setMethod("[", signature = c(x = "Bar", i = "ANY", j = "ANY", drop = "ANY"),
function(x, i, j, ..., drop = TRUE) {
x <- x@vec
callGeneric()
})
其中callGeneric
实现了您试图用match.call
和eval
实现的东西,但以更健壮的方式实现.
bar.v[]
## [1] 1 2 3
bar.v[-2L]
## [1] 1 3
bar.v[1L, 1L, drop = FALSE]
## Error in x[i = i, j = j, drop = drop] : incorrect number of dimensions
bar.a[1L, 1L, drop = FALSE]
## [,1]
## [1,] 1
还可以基于callGeneric
为Foo
定义一个方法,但是对于一个纯粹的类似向量的类,callGeneric
的开销可能是一个障碍.早期的只使用nargs
和missing
的方法要快得多,我们通常希望对[
的方法进行严格优化,因为它们往往是在循环中调用的.
microbenchmark::microbenchmark(foo.v[-2L], bar.v[-2L], times = 1000L)
## Unit: nanoseconds
## expr min lq mean median uq max neval
## foo.v[-2L] 861 1148 1385.062 1271 1476 11808 1000
## bar.v[-2L] 12382 14842 15671.225 15498 16031 77613 1000
备注
上述两个setMethod
看涨期权遵循两个良好做法:
- 方法的形参与泛型函数的形参匹配.
- 为清楚起见,签名指定了每个正式参数的类别.省略的正式参数在没有警告(叹息)的情况下获得
"ANY"
类,这让许多刚接触S4的人感到惊讶.