我想为基矩阵子类编写一个%*%
方法。我的子类是S3类,help("%*%")
的留档说%*%
是S4泛型。在使用方法::setOldClass()
和方法之前,我已经为S4泛型方法编写了S3类的方法::setMethod()
并且我已经查看了{Matrix}
包的源代码以获得灵感,但是由于某种原因,我不能完全让它在这种情况下工作。方法::show方法()
显示我的方法存在于我的目标签名中,但是当我尝试使用它时,我的方法似乎从来没有被R调用。
x <- diag(3)
class(x) <- c("atm2d", class(matrix()))
print(x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
attr(,"class")
[1] "atm2d" "matrix" "array"
默认的%*%
摆脱了我的类属性,我想保留它。
print(x %*% x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
我尝试为我的类创建一个%*%
方法,它不会摆脱我的类属性:
as.matrix.atm2d <- function(x, ...) {
class(x) <- NULL
x
}
matmult <- function(x, y) {
v <- as.matrix(x) %*% as.matrix(y)
class(v) <- c("atm2d", class(matrix()))
v
}
methods::setOldClass("atm2d")
# methods::setOldClass(c("atm2d", class(matrix()))) # also doesn't work for me
methods::setMethod("%*%", c(x = "atm2d", y = "atm2d"), function(x, y) matmult(x, y))
show方法()
似乎显示了一个创建了预期签名的S4方法:
showMethods("%*%", class = "atm2d")
Function: %*% (package base)
x="atm2d", y="atm2d"
然而,这个方法实际上似乎并没有被%*%
调用:
print(x %*% x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
如果我的方法被调用,我会期望它也打印出它的类:
print(matmult(x, x))
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
attr(,"class")
[1] "atm2d" "matrix" "array"
%*%
运算符在内部是通用的,这意味着调度发生在C代码中。目前(即在R 4.2.3中),相应的C函数do_matprod
(此处定义)包含以下检查:
if (PRIMVAL(op) == 0 && /* %*% is primitive, the others are .Internal() */
(IS_S4_OBJECT(x) || IS_S4_OBJECT(y))
&& R_has_methods(op)) {
SEXP s, value;
/* Remove argument names to ensure positional matching */
for(s = args; s != R_NilValue; s = CDR(s)) SET_TAG(s, R_NilValue);
value = R_possible_dispatch(call, op, args, rho, FALSE);
if (value) return value;
}
如果x
和y
都不是S4对象,如您的示例所示,那么do_matprod
继续将它们作为传统矩阵处理,而不是查看任一参数的class
属性。您引用的help("%*%")
部分:
此运算符是S4泛型但不是S3泛型。需要为名为x
和y
的两个参数的函数编写S4方法。
试图表达这一点,但不是特别清楚。(毕竟你确实定义了一个S4方法。)
这里有两个主要问题:
>
setOldClass
允许您在签名中使用S3类定义S4方法,但内部泛型函数仅在其中一个参数是S4对象时查找S4方法(为了速度)。
%*%
不是S3通用的,因此即使您注册了像%*%. zzz
这样的S3方法,它也不会被分派。
话虽如此,R-core已经promise在不久的将来使%*%
运算符S3通用。如果发生这种情况,那么%*%
的行为将类似于和
Ops
组的其他内部通用成员,因为S3方法%*%. zzz
将在适当的地方被分派。但是,当两个参数都不是S4对象时,S4方法仍然不会被分派。
可以说,当您尝试定义永远不会分派的S4方法时,应该更改setMethod
以发出警告或错误信号,就像您在示例中所说的那样。
枚举泛型函数的类型和它们调度的方法类型可能会有所帮助,从而将注意力限制在S3和S4上。我们将使用此脚本为我们的测试定义对象,每个对象都应该在新的R进程中运行:
## objects.R
w <- structure(0, class = "a")
x <- structure(0, class = "b")
setOldClass("c")
y <- structure(0, class = "c")
setClass("d", contains = "numeric")
z <- new("d", 0)
这些通过UseMethod
为S3类和S4类调度S3方法。当没有找到方法时,它们会调度默认方法*. default
或(如果没有找到)抛出错误。他们从不调度S4方法。
source("objects.R")
h <- .__h__. <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")
.S3method("h", "a", function(x) "a")
.S3method("h", "b", function(x) "b")
setMethod("h", "c", function(x) "c")
setMethod("h", "d", function(x) "d")
h <- .__h__. # needed to undo side effect of 'setMethod'
h
## function(x) UseMethod("h")
h(w)
## [1] "a"
h(x)
## [1] "b"
h(y)
## [1] "default"
h(z)
## [1] "default"
这些分派S3类(用setOldClass
正式定义)和S4类的S4方法通过标准通用
。当没有找到方法时,它们分派默认方法*@default
。如果默认方法是S3通用的,那么再次分派,这次分派到任何可用的S3方法。但是,通常默认方法只是调用stop
来发出错误信号。
source("objects.R")
h <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")
.S3method("h", "c", function(x) "c")
setMethod("h", "d", function(x) "d")
h
## standardGeneric for "h" defined from package ".GlobalEnv"
##
## function (x)
## standardGeneric("h")
## <environment: 0x1044650b0>
## Methods may be defined for arguments: x
## Use showMethods(h) for currently available ones.
h@default
## Method Definition (Class "derivedDefaultMethod"):
##
## function (x)
## UseMethod("h")
##
## Signatures:
## x
## target "ANY"
## defined "ANY"
h(w)
## [1] "default"
h(x)
## [1] "default"
h(y)
## [1] "c"
h(z)
## [1] "d"
这些都是在base中定义的。您可以参考帮助页面或源代码来确定它们是否仅是S3泛型,仅是S4泛型,还是S3和S4泛型兼而有之。在第三种情况下,只有在没有找到合适的S4方法时,才会发生S3分派。而且,正如我已经解释过的,只有当签名中的一个参数是S4对象时,才会发生S4分派。
让我们以和
%*%
为例。两者都是S4通用的,但只有是S3通用的。
source("objects.R")
.S3method("+", "default", function(e1, e2) "default")
.S3method("+", "a", function(e1, e2) "a3")
.S3method("+", "b", function(e1, e2) "b3")
.S3method("+", "c", function(e1, e2) "c3")
.S3method("+", "d", function(e1, e2) "d3")
setMethod("+", c("c", "c"), function(e1, e2) "c4")
setMethod("+", c("d", "d"), function(e1, e2) "d4")
w + w
## [1] "a3"
x + x
## [1] "b3"
y + y
## [1] "c3"
z + z
## [1] "d4"
source("objects.R")
.S3method("%*%", "default", function(x, y) "default")
.S3method("%*%", "a", function(x, y) "a3")
.S3method("%*%", "b", function(x, y) "b3")
.S3method("%*%", "c", function(x, y) "c3")
.S3method("%*%", "d", function(x, y) "d3")
setMethod("%*%", c("c", "c"), function(x, y) "c4")
setMethod("%*%", c("d", "d"), function(x, y) "d4")
w %*% w
## [,1]
## [1,] 0
x %*% x
## [,1]
## [1,] 0
y %*% y
## [,1]
## [1,] 0
z %*% z
## [1] "d4"