r语言 - 为不兼容的类定义S3 Group泛型



假设我正在实现一个自定义S3类,名为"myclass":

myvec <- 1:5
class(myvec) <- "myclass"

我定义了Group Ops泛型,以便在标准操作之后保留类。

Ops.myclass <- function(e1, e2) {
if(is(e1, "myclass")) {
e1 <- unclass(e1)
structure(NextMethod(), class="myclass")
} else if(is(e2, "myclass")) {
e2 <- unclass(e2)
structure(NextMethod(), class="myclass")
}
}

现在我的类保留了,例如,添加:

1 + myvec  # still has myclass
myvec + 1  # still has myclass

但是,当我对具有自己的Group泛型的对象执行此操作时,就会遇到问题:

myvec + Sys.Date()
[1] 19454 19455 19456 19457 19458
attr(,"class")
[1] "myclass"
Warning message:
Incompatible methods ("Ops.myclass", "+.Date") for "+" 

不仅产生警告,而且结果也不同:

unclass(myvec) + Sys.Date()
[1] "2023-04-07" "2023-04-08" "2023-04-09" "2023-04-10" "2023-04-11"

我如何解决这个警告,并使此操作返回相同的结果,它将返回,如果myvec没有一个类?

基本上我希望myclass有它自己的组泛型,但在发生冲突的情况下服从,并在碰撞时优先考虑其他类。

唉,你的运气不好。4.3.0. 我建议定义如下的S4方法:

setOldClass("zzz")
setMethod("Ops", c("zzz", "zzz"), function(e1, e2) <do stuff>)
setMethod("Ops", c("zzz", "ANY"), function(e1, e2) <do stuff>)
setMethod("Ops", c("ANY", "zzz"), function(e1, e2) <do stuff>)

因为S4泛型函数在S3调度之前执行S4调度(如果它们也是S3泛型的话)。那么,理论上,S3分派歧义将永远不会被检测到。

但是后来我想起Ops组的所有成员在内部都是泛型的,所以当参数都不是S4对象时,不要分派S4方法,就像<zzz> + <Date>的情况一样。

R 4.3.0引入了一个新的泛型函数chooseOpsMethod,允许用户指定如何解决Ops组成员的S3调度歧义。它在?Ops?chooseOpsMethod中都有记录。我假设你已经阅读了相关章节,并建议你这样做:

.S3method("chooseOpsMethod", "zzz", 
function(x, y, mx, my, cl, reverse) TRUE)
.S3method("Ops", "zzz",
function(e1, e2) {
if (inherits(e1, "zzz")) {
class(e1) <- NULL
cl <- oldClass(e2)
} else {
class(e2) <- NULL
cl <- oldClass(e1)
}
r <- callGeneric(e1, e2)
## Do not assign a class to 'r' if the "other" argument inherits
## from a class with a method for this generic function ...
if (is.null(cl) ||
(all(match(paste0(   "Ops", ".", cl), .S3methods(   "Ops"), 0L) == 0L) && 
all(match(paste0(.Generic, ".", cl), .S3methods(.Generic), 0L) == 0L)))
class(r) <- "zzz"
r
})
x <- structure(0:5, class = "zzz")
x + x
## [1]  0  2  4  6  8 10
## attr(,"class")
## [1] "zzz"
x + 0
## [1] 0 1 2 3 4 5
## attr(,"class")
## [1] "zzz"
0 + x
## [1] 0 1 2 3 4 5
## attr(,"class")
## [1] "zzz"
x + .Date(0L)
## [1] "1970-01-01" "1970-01-02" "1970-01-03" "1970-01-04" "1970-01-05" "1970-01-06"
.Date(0L) + x
## [1] "1970-01-01" "1970-01-02" "1970-01-03" "1970-01-04" "1970-01-05" "1970-01-06"

只在没有chooseOpsMethod.DatechooseOpsMethod.default无条件返回FALSE的情况下才有效。其他人可能会出现并注册一个返回TRUEchooseOpsMethod.Date,破坏<Date> + <zzz>的行为,但这是您依靠S3而不是S4所承担的风险…