R6类的S4调度行为不一致

Rap*_*ter 4 inheritance r s4 reference-class r6

实际问题

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

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

背景和例子

参考类

请考虑以下示例,您希望在其中定义在超类上调度所有Reference类的实例继承自(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`"
Run Code Online (Sandbox Code Playgroud)

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

class(TestRefClass$new())
[1] "TestRefClass"
attr(,"package")
[1] ".GlobalEnv"
Run Code Online (Sandbox Code Playgroud)

但是,查看类生成器对象的属性会显示它:

> attributes(TestRefClass)
[... omitted ...]

 Reference Superclasses:  
    "envRefClass"

[... omitted ...]
Run Code Online (Sandbox Code Playgroud)

这就是调度工作的原因

R6课程

当你想为R6类做类似的事情时,事情似乎并不是直截了当的,即使它们最初是这样的(与Reference Classes相比):

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"’
Run Code Online (Sandbox Code Playgroud)

通过"直接出现"我的意思是,class()实际上表明所有R6类都继承自R6可以用作方法调度的超类的类:

class(TestR6$new())
[1] "TestR6" "R6"  
Run Code Online (Sandbox Code Playgroud)

R6Class()实际上,帮助页面显示该类R6只是作为非正式的S3类添加class = TRUE.这也是为什么在尝试为此类定义S4方法时出现警告的原因.

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

  1. 通过将课程R6变成正式课程setOldClass()
  2. 让所有R6类的实例继承自其他一些超类,比方说, .R6

广告1)

setOldClass("R6")
> isClass("R6")
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

当在类表/图形中以S3样式进行黑客攻击时,这会起作用:

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

但是,实际的R6类实例失败了:

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

广告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"’
Run Code Online (Sandbox Code Playgroud)

结论

虽然方法1排序在"灰色区域"中运行以使S3和S4稍微兼容,但是方法2似乎是IMO应该工作的完全有效的"纯S4"解决方案.如果R6类的实现与R中的非正式/正式类和方法调度的交互存在不一致,那么这并不是我提出问题的事实.

Rap*_*ter 7

由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()))
Run Code Online (Sandbox Code Playgroud)

但是,AFAICT,这意味着对于您的包,您需要确保setOldClass()以这种方式调用您希望S4方法工作的所有 R6类.

这可以通过在函数中捆绑这些调用来完成.onLoad().onAttach()(见这里):

.onLoad <- function(libname, pkgname) {
  setOldClass(c("TestR6_1", "R6"))
  setOldClass(c("TestR6_2", "R6"))
  setOldClass(c("TestR6_3", "R6"))
}
Run Code Online (Sandbox Code Playgroud)

这假设你已经定义了三个R6类(TestR6_1通过TestR6_3)