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

标签 r methods namespaces r-s4 r6

实际问题

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

详细信息

如果 R6 类已在 .GlobalEnv 中定义(如使用 source() 进行采购时)和 setOldClass(),则一切正常。 code> 也可通过 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")

使用 where = .GlobalEnv 调用 setOldClass()(where 的默认值):

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

当 R6 类在包的命名空间内定义时(例如使用 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))

但是,这给我带来了以下错误:

Error in setOldClass(c("Module", "Api"), where = where) : inconsistent old-style class information for “Module”; the class is defined but does not extend “Api” and is not valid as the data part


促进可重复性

我试图让这个问题尽可能容易地重现,所以你会在我的 GitHub repository 中找到 r6.s4 包。

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

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

最佳答案

我想我已经明白了。

经验教训

  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 的方法,它大量依赖于实际的生成器对象,而不是单纯的类名,因为它使您能够使用 :: ,从而使类与所有其他包组件一样井井有条。但不幸的是,当通过 setOldClass() 为它们注册 S4 等效项时,该范例似乎丢失了。这让我想起了我以前对 name clashes for classes 风险不断增加的提示。 - *叹息*

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

关于注册 R6 类的 S4 等效项,同时保留继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29153289/

相关文章:

c++ - 斯坦福一体化

namespaces - 如何获取 MediaWiki namespace 列表?

php - 如何管理 Netbeans 中的默认命名空间

java - 在java中使用一个方法内生成的本体到另一个方法

scala - 将泛型方法分配给 Scala 中的变量

java - 为什么方法名以0结尾?

php - 如何在 WordPress 插件中使用命名空间和类

r - 使用 data.table 按变量分组查找均值差

删除数据框中任意行数的最后 N 行

r - 按列合并这些列的不同值