假设我正在实现一个自定义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.Date
和chooseOpsMethod.default
无条件返回FALSE
的情况下才有效。其他人可能会出现并注册一个返回TRUE
的chooseOpsMethod.Date
,破坏<Date> + <zzz>
的行为,但这是您依靠S3而不是S4所承担的风险…