2

实际问题

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

细节

在 R6 类已在中定义.GlobalEnv(如使用 采购时source())并且setOldClass()也使用 调用的情况下,一切正常where = .GlobalEnv

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

在中定义 R6 类.GlobalEnv

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 类在包的命名空间中定义时(如使用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("Module", "Api"), where = where) 中的错误:“Module”的旧式类信息不一致;该类已定义但未扩展“Api”,并且作为数据部分无效


促进再现性

我试图让这个问题尽可能容易重现,所以你可以在我的GitHub 存储库r6.s4中找到该包

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

此外,这个单元测试可能有助于弄清楚发生了什么。

4

1 回答 1

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依赖实际生成器对象而不是单纯的类名方法,因为它使您能够使用::并因此保持类与所有其他包组件一样有组织。但不幸的是,当通过setOldClass(). 这让我回到了我关于班级名称冲突风险增加的旧抱怨- *sigh*.

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

于 2015-03-20T01:32:34.263 回答