实际问题
当所有类都需要位于包的命名空间中(而不是在 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可能有助于弄清楚发生了什么。
最佳答案
我想我已经明白了。
经验教训
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
调用
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)) }) }
在
.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)) }) }
我喜欢
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/