R6类的S4调度行为不一致

4

实际问题

  1. R6类继承(非正式的S3)类R6,这个事实是否允许为该类的签名参数定义S4方法?

  2. 鉴于当前S3/S4标准,如果不是这种情况,有什么解决方法,或者在这种情况下可以被视为“最佳实践”?

背景和示例

引用类

考虑以下示例,您想定义在所有引用类实例都继承自的超类(envRefClass)上分派的方法:

TestRefClass <- setRefClass("TestRefClass", fields= list(.x = "numeric"))
setGeneric("foo", signature = "x",
  def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "envRefClass"),
  definition = function(x) {
    "I'm the method for `envRefClass`"
})
> try(foo(x = TestRefClass$new()))
[1] "I'm the method for `envRefClass`"

这种继承结构并不是直接显而易见的,因为class()并不会揭示这一事实:

class(TestRefClass$new())
[1] "TestRefClass"
attr(,"package")
[1] ".GlobalEnv"

然而,查看类生成器对象的属性可以揭示它的一些特点:
> attributes(TestRefClass)
[... omitted ...]

 Reference Superclasses:  
    "envRefClass"

[... omitted ...]

这就是为什么调度程序能正常运作的原因。 R6类 当你想要对R6类做类似的事情时,事情似乎并不像引用类那样简单明了:
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setMethod("foo", c(x = "R6"),
  definition = function(x) {
    "I'm the method for `R6`"
})
> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘foo’ for signature ‘"TestR6"

“看上去很简单”的意思是class()实际上暗示了所有的R6类都继承自类R6,可以作为方法分派的超类:

class(TestR6$new())
[1] "TestR6" "R6"  
R6Class()的帮助页面实际上显示,只要class = TRUE,类R6就会作为非正式S3类添加进来。这也是为什么在试图为这个类定义S4方法时会出现警告的原因。
因此,这基本上给我们留下了两种可能的选择/解决方案:
  1. 通过setOldClass()将类R6转换成一个正式的类。
  2. 让所有的R6类实例都继承自其他的超类,比如.R6
注1)
setOldClass("R6")
> isClass("R6")
[1] TRUE

当在类表/图中以S3风格进行编码时,此方法可行:

dummy <- structure("something", class = "R6")
> foo(dummy)
[1] "I'm the method for `R6`"

然而,它在实际的R6类实例中失败:

> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for functionfoofor signature ‘"TestR6"’

广告2)

.R6 <- R6Class(".R6")
TestR6_2 <- R6Class("TestR6_2", inherit = .R6, public = list(.x = "numeric"))
setMethod("foo", c(x = ".R6"),
  definition = function(x) {
    "I'm the method for `.R6`"
})
> try(foo(x = TestR6_2$new()))
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘foo’ for signature ‘"TestR6_2"

结论

虽然方法1的排序在S3和S4之间存在"灰色地带",使得S3和S4有些兼容性,但是方法2似乎是一种完全有效的"纯S4"解决方案,在我看来应该可以正常工作。事实上,这让我开始质疑在R中非正式/正式类和方法派发的交互实现中是否存在不一致。

1个回答

9
由于Hadley Wickham的帮助,我发现当包含继承结构时,setOldClass()实际上解决了这个问题。
require("R6")
setOldClass(c("TestR6", "R6"))
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setGeneric("foo", signature = "x",
  def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "R6"),
  definition = function(x) {
    "I'm the method for `R6`"
  })
try(foo(x = TestR6$new()))

然而,据我所知,这意味着对于您的软件包,您需要确保以这种方式调用setOldClass(),以使您希望其S4方法起作用的所有R6类都能够正常工作。
这可以通过在函数.onLoad().onAttach()(参见此处)中捆绑这些调用来完成。
.onLoad <- function(libname, pkgname) {
  setOldClass(c("TestR6_1", "R6"))
  setOldClass(c("TestR6_2", "R6"))
  setOldClass(c("TestR6_3", "R6"))
}

这里假设您已经定义了三个R6类(TestR6_1TestR6_3)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接