为不兼容的类定义S3组泛型

huangapple go评论60阅读模式
英文:

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 &lt;- 1:5
class(myvec) &lt;- &quot;myclass&quot;

I define Group Ops generics so that the class is preserved after standard operations.

Ops.myclass &lt;- function(e1, e2) {
  if(is(e1, &quot;myclass&quot;)) {
    e1 &lt;- unclass(e1)
    structure(NextMethod(), class=&quot;myclass&quot;)
  } else if(is(e2, &quot;myclass&quot;)) {
    e2 &lt;- unclass(e2)
    structure(NextMethod(), class=&quot;myclass&quot;)
  }
}

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(,&quot;class&quot;)
[1] &quot;myclass&quot;
Warning message:
Incompatible methods (&quot;Ops.myclass&quot;, &quot;+.Date&quot;) for &quot;+&quot; 

Not only does it produce a warning, but in addition the result is different:

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

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 而起作用。其他人可以注册一个返回 TRUEchooseOpsMethod.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(&quot;zzz&quot;)
setMethod(&quot;Ops&quot;, c(&quot;zzz&quot;, &quot;zzz&quot;), function(e1, e2) &lt;do stuff&gt;)
setMethod(&quot;Ops&quot;, c(&quot;zzz&quot;, &quot;ANY&quot;), function(e1, e2) &lt;do stuff&gt;)
setMethod(&quot;Ops&quot;, c(&quot;ANY&quot;, &quot;zzz&quot;), function(e1, e2) &lt;do stuff&gt;)

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

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 &lt;Date&gt; + &lt;zzz&gt;, but that is the risk you take by relying on S3 instead of S4 ...

huangapple
  • 本文由 发表于 2023年4月7日 00:01:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75951534.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定