英文:
Define S3 Group generics for incompatible classes
问题
以下是您要翻译的代码部分:
说,我正在实现一个自定义的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 # 仍然具有myclass
myvec + 1 # 仍然具有myclass
然而,当我对具有其自己的Group通用函数的对象执行此操作时,会出现问题:
myvec + Sys.Date()
[1] 19454 19455 19456 19457 19458
attr(,"class")
[1] "myclass"
警告信息:
不兼容的方法("Ops.myclass","+.Date")用于"+"
它不仅产生警告,而且结果也不同:
unclass(myvec) + Sys.Date()
[1] "2023-04-07" "2023-04-08" "2023-04-09" "2023-04-10" "2023-04-11"
**问题**:如何解决此警告并使此操作返回与`myvec`没有类时返回的相同结果?
基本上,我希望`myclass`具有自己的Group通用函数,但在发生冲突时表现得服从,并优先考虑其他类。
英文:
Say, I am implementing a custom S3 class, called "myclass":
myvec <- 1:5
class(myvec) <- "myclass"
I define Group Ops generics so that the class is preserved after standard operations.
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")
}
}
Now my class is preserved with, for example, addition:
1 + myvec # still has myclass
myvec + 1 # still has myclass
However, when I do this for objects that have their own Group generics, I get into issues:
myvec + Sys.Date()
[1] 19454 19455 19456 19457 19458
attr(,"class")
[1] "myclass"
Warning message:
Incompatible methods ("Ops.myclass", "+.Date") for "+"
Not only does it produce a warning, but in addition the result is different:
unclass(myvec) + Sys.Date()
[1] "2023-04-07" "2023-04-08" "2023-04-09" "2023-04-10" "2023-04-11"
Question: How can I resolve this warning and make this operation return the same result it would return if myvec
didn't have a class?
Basically I want myclass
to have it's own Group Generics, but act subserviently in case of a conflict and give priority to the other class, when in collision.
答案1
得分: 1
AFAICT,你在 R < 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 对象时,比如 <zzz> + <Date>
情况下,它们不会分派 S4 方法。
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)
## 如果“other”参数继承自具有此通用函数方法的类,不要为“r”分配类...
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 的风险...
英文:
AFAICT, you are out of luck with R < 4.3.0. I was going to suggest defining S4 methods like:
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>)
because S4 generic functions perform S4 dispatch before S3 dispatch (if they are also S3 generic). Then, in theory, S3 dispatch ambiguities would never be detected.
But then I remembered that all members of the Ops
group are internally generic and so do not dispatch S4 methods when neither argument is an S4 object, as in the <zzz> + <Date>
case.
R 4.3.0 introduces a new generic function chooseOpsMethod
to allow users to specify how S3 dispatch ambiguities should be resolved for members of the Ops
group. It is documented in ?Ops
and of course in ?chooseOpsMethod
. I will assume that you have read the relevant sections and just suggest this:
.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"
That works only because there is no chooseOpsMethod.Date
and because chooseOpsMethod.default
returns FALSE
unconditionally. Someone else could come along and register a chooseOpsMethod.Date
returning TRUE
, spoiling the behaviour of <Date> + <zzz>
, but that is the risk you take by relying on S3 instead of S4 ...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论