我想为一个基矩阵子类编写一个%*%方法.我的子类是一个S3类,help("%*%")的文档说明%*%是一个S4泛型.在使用methods::setOldClass()methods::setMethod()之前,我已经为S4泛型方法编写了S3类的方法,并查看了{Matrix}包的源代码以获得灵感,但由于某些原因,我无法在这种情况下使用它.methods::showMethods()显示我的方法存在于我的目标签名,但当我try 使用它时,我的方法似乎从未被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" 

缺省的%*%go 掉了我的CLASS属性,我想保留它.

print(x %*% x)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

我try 为我的类创建一个%*%方法,它不会go 掉我的类属性:

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")
# this alternate `setOldClass()` also doesn't work
# methods::setOldClass(c("atm2d", class(matrix()))) 
methods::setMethod("%*%", 
                   c(x = "atm2d", y = "atm2d"), 
                   function(x, y) matmult(x, y))

showMethods()似乎显示创建了具有预期签名的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(定义here)包含该判断:

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;
}

如果xy都不是S4对象,如您的示例所示,则do_matprod继续将它们作为传统矩阵处理,not查看任一参数的class属性.你所指的第help("%*%")节:

该运算符是S4通用的,但不是S3通用的.需要为名为xy的两个参数的函数编写S4方法.

试图表达这一点,但不是特别清楚.(毕竟,您did定义了S4方法.)

这里有两个主要问题:

  • setOldClass允许您在签名中使用S3类定义S4方法,但在内部泛型函数仅在其中一个参数是S4对象时查找S4方法(为了速度).

  • %*%不是S3泛型,所以即使您注册了像%*%.zzz这样的S3方法,它也不会被调度.

话虽如此,R-core已经promise 让%*%运营商S3成为通用in the near future.如果发生这种情况,则%*%的行为将类似于+Ops组的其他内部通用成员,因为S3方法%*%.zzz将在适当的地方被调度.但是,当两个参数都不是S4对象时,S4方法仍然不会被调度.

可以说,当您try 定义永远不会被调度的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)

Non-internally S3 generic functions

这些方法通过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) "a3")
setMethod("h", "c", function(x) "c4")
setMethod("h", "d", function(x) "d4")

h <- .__h__. # needed to undo side effect of 'setMethod'
h
## function(x) UseMethod("h")

h(w)
## [1] "a3"
h(x)
## [1] "default"
h(y)
## [1] "default"
h(z)
## [1] "default"

Non-internally S4 generic functions

这些方法通过standardGeneric为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) "c3")
setMethod("h", "d", function(x) "d4")

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] "c3"
h(z)
## [1] "d4"

Internally generic functions

这些都在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"

在这里,S3方法被调度.前三个结果是通过S3调度获得的.最终结果通过S4调度获得.

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"

在这里,S3方法是not个分派的.前三个结果是通过内部定义的默认方法获得的.最终结果通过S4调度获得.

R相关问答推荐

DT::可数据的正规表达OR运算符问题

geom_raster不适用于x比例中超过2,15的值

为什么以及如何修复Mapview不显示所有点并且st_buffer合并一些区域R?

根据shiny 应用程序中的数字输入更改图标 colored颜色

R Lubridate:舍入/快照日期时间到一天中最近的任意时间?

如何在ggplot 2线性图的每个方面显示每个组的误差条?

工作流程_set带有Dplyrr风格的 Select 器,用于 Select 结果和预测因子R

咕噜中的元素列表:map

找出疾病消失的受试者

在R中,如何在每个堆叠的条上放置误差条,特别是当使用facet_grid时?

如何根据嵌套元素的名称高效而优雅地确定它属于哪个列表?

对于变量的每个值,仅 Select 包含列表中所有值的值.R

如何在R中描绘#符号?

在多页PDF中以特定布局排列的绘图列表不起作用

R:如果为NA,则根据条件,使用列名模式将缺少的值替换为另一列中的值

调换行/列并将第一行(原始数据帧的第一列)提升为标题的Tidyr类似功能?

数据集上的R循环和存储模型系数

在使用SliderInput In Shiny(R)设置输入数据的子集时,保留一些情节痕迹

使用一个标签共享多个组图图例符号

希望解析和复制R中特定模式的数据