r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
407 stars 56 forks source link

Error when registering S4 equivalents for R6 classes while preserving inheritance #50

Closed jankowtf closed 7 years ago

jankowtf commented 9 years ago

Sorry, me again with another question of how to bridge R6/S3 and S4 ;-) It's related to this issue and I'm aware that it's not actually caused by R6 but rather "S3 vs. S4". I would nevertheless very much appreciate it if you could shed some light on what's exactly going wrong here. Thanks a lot!


Actual question

How can I turn a bunch of R6 classes that inherit from each other into S4 classes while preserving the inheritance structure when all classes need to live in a package's namespace (as opposed to in GlobalEnv)?

Details

Everything works fine in cases where the R6 classes have been defined in .GlobalEnv (as when sourcing with source()) and setOldClass() is also called with where = .GlobalEnv.

But I cannot get it to work when the R6 classes have been defined inside a package's namespace (as when calling devtools::load_all()):

Defining the R6 classes in .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")

Calling setOldClass() with where = .GlobalEnv (the default for where):

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

When the R6 classes are defined inside a package's namespace (as when "sourcing" with devtools::load_all() instead of source()), I assumed that I need to account for that by providing an explicit 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))

However, that leaves me with the following error:

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


Facilitating reproducability

I tried to make this issue as easily reproducible as possible, so you'll find the r6.s4 package at my GitHub repository

Note again that you have to run devtools::load_all() (or hit CRTL + SHFT + L in RStudio) in order to reproduce the error.

Also, this unit test might help in figuring out what's going on.

jankowtf commented 9 years ago

Phew, that was kind of a tough one for me - but I think I got it figured out now: http://stackoverflow.com/questions/29153289/turning-r6-classes-into-s4-classes-while-preserving-inheritance/29157950#29157950

Any comments on how one might go about point 4, or in other words, avoid point 1? I really like your approach of relying on actual objects rather than mere class names a lot as it enables you to use :: for classes - awesome!!

If it weren't for S4 being able to dispatch based on multiple signature arguments instead of just one I would totally switch back to S3 - but I need just that so often :-/

hadley commented 7 years ago

This doesn't seem like an R6 problem as it simply sets the class attribute. I think it's just a matter of figuring out the correct place to call setOldClass()