在保留继承的同时注册S4等效的R6类

时间:2015-03-19 19:25:37

标签: r methods namespaces s4 r6

实际问题

如果所有类都需要存在于包的命名空间中(而不是GlobalEnv中),那么如何将一堆相互继承的R6类转换为S4类,同时保留继承结构?

详细

.GlobalEnv中定义R6类(如使用source()进行采购时)和使用setOldClass()调用where = .GlobalEnv时,一切正常。

但是当在包的命名空间中定义R6类时(如调用devtools::load_all()时),我无法使它工作:

.GlobalEnv中定义R6类:

Object <- R6Class("Object", portable = TRUE, public = list(
  foo = function() "foo")
)
Api <- R6Class("Api", inherit = Object, portable = TRUE,
  public = list(bar = function() "bar")
)
Module <- R6Class("Module", inherit = Api, portable = TRUE,
  public = list(fooBar = function() "fooBar")

使用setOldClass()where = .GlobalEnv的默认设置)呼叫where

setOldClass(c("Object", "R6"))
setOldClass(c("Api", "Object"))
setOldClass(c("Module", "Api"))

当R6类在包的名称空间内定义时(如何&#34;采购&#34;使用devtools::load_all()而不是source()),我认为我需要帐户为此提供明确的where

where <- if ("package:r6.s4" %in% search()) {
  as.environment("package:r6.s4")
} else {
  .GlobalEnv
}
try(setOldClass(c("Object", "R6"), where = where))
try(setOldClass(c("Api", "Object"), where = where))
try(setOldClass(c("Module", "Api"), where = where))

然而,这让我有以下错误:

  

setOldClass中出错(c(&#34;模块&#34;,&#34; Api&#34;),其中= where):     “模块”的旧式类信息不一致;该类已定义但不扩展“Api”,并且无效作为数据部分


促进再现性

我尝试尽可能轻松地重现此问题,因此您可以在GitHub repository

找到r6.s4

请再次注意,您必须运行devtools::load_all()(或点击RStudio中的CRTL + SHFT + L)才能重现错误

此外,这unit test可能有助于弄清楚发生了什么。

1 个答案:

答案 0 :(得分:1)

我想我明白了。

经验教训

  1. setOldClass(c("Module", "Api"))失败的原因是由于包Rcpp具有定义了相同名称的类。

    require("R6")
    > getClass("Module")
    Class "Module" [package "Rcpp"]
    
    Slots:
    
      Name:       .xData
    Class: environment
    
    Extends: 
      Class ".environment", directly
    Class "environment", by class ".environment", distance 2, with explicit coerce
    Class "refObject", by class ".environment", distance 3, with explicit coerce
    
  2. 调用setOldClass()的最佳位置似乎在.onAttach()内,因为在此阶段包已完全加载,因此存在where参数可以存在的命名空间环境指向。

    .onAttach <- function(libname, pkgname) {
      where <- as.environment("package:r6.s4")
      clss <- list(
        c("Object", "R6"),
        c("Api", "Object"),
        c("Module2", "Api")
      )
      sapply(clss, function(cls) {
        try(setOldClass(cls, where = where))
      })
    }
    
  3. .onAttach()内,您需要保持良好状态,而不是“重载”setOldClass()在先前的包加载上设置的类。这就是为什么类似的东西可能有意义的原因:

     .onAttach <- function(libname, pkgname) {
      where <- as.environment("package:r6.s4")
      clss <- list(
        c("Object", "R6"),
        c("Api", "Object"),
        c("Module2", "Api")
      )
      sapply(clss, function(cls) {
        idx <- sapply(cls, isClass)
        try(sapply(cls[idx], removeClass, where = where))
        try(setOldClass(cls, where = where))
      })      
    }
    
  4. 我喜欢R6依靠实际的生成器对象而不仅仅是类名很多的方法,因为它使您能够使用{{ 1}}因此保持类与所有其他包组件一样有条理。但遗憾的是,当通过::为他们注册S4等价物时,这种范例似乎已经丢失了。这种情况让我回到了我对name clashes for classes - setOldClass()日益增加的风险的抱怨。

  5. 对于那些对我的试错过程的细节感兴趣的人:我试图将包变成一种自我引用。检查文件R/classes.rtests/testthat/test-S4.r以及有关如何检查和处理名称冲突R/name_clashes.r的原型代码。