R6类的S4调度行为不一致

时间:2014-12-09 17:11:59

标签: r inheritance s4 reference-class r6

实际问题

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

  2. 因为这是 - AFAICT - 不是这样,什么是符合当前S3 / S4标准的解决方法,或者在某些情况下可能被视为“最佳实践”?

  3. 背景和示例

    参考类

    考虑以下示例,您希望定义在超类上调度Reference Classes的所有实例都从(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()的帮助页面实际上显示,只要R6,类class = TRUE仅作为非正式的S3类添加。这也是为什么在尝试为这个类定义S4方法时会出现警告的原因。

    那么这基本上给我们留下了两个可能的选项/解决方法:

    1. 通过R6
    2. 将课程setOldClass()变成正式课程
    3. 让所有R6类的实例都继承自其他超类,例如.R6
    4. 广告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 function ‘foo’ for signature ‘"TestR6"’
      

      Ad 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稍微兼容,但是方法2似乎是IMO应该工作的完全有效的“纯S4”解决方案。如果R6类的实现与R中的非正式/正式类和方法调度的交互存在不一致,则不会引起我的提问。

1 个答案:

答案 0 :(得分:6)

由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()))

但是,AFAICT,这意味着对于您的软件包,您需要确保以这种方式调用{em>所有您想要用于其中的R6类的<{1}}工作方法。

可以通过在函数setOldClass().onLoad()中捆绑这些调用来完成此操作(请参阅here):

.onAttach()

这假设您已经定义了三个R6类(.onLoad <- function(libname, pkgname) { setOldClass(c("TestR6_1", "R6")) setOldClass(c("TestR6_2", "R6")) setOldClass(c("TestR6_3", "R6")) } TestR6_1