问题
Actual questions
Shouldn't the fact that R6 classes inherit from (informal S3) class
R6
allow the definition of S4 methods for signature arguments of that very class?As this is - AFAICT - not the case, what would be a workaround that is in line with current S3/S4 standards or that could somewhat be regarded as "best practice" in such situations?
Background and example
Reference Classes
Consider the following example where you would like to define methods that dispatch on the superclass that all instances of Reference Classes inherit from (envRefClass
):
TestRefClass <- setRefClass("TestRefClass", fields= list(.x = "numeric"))
setGeneric("foo", signature = "x",
def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "envRefClass"),
definition = function(x) {
"I'm the method for `envRefClass`"
})
> try(foo(x = TestRefClass$new()))
[1] "I'm the method for `envRefClass`"
This inheritance structure is not directly obvious as class()
won't reveal that fact:
class(TestRefClass$new())
[1] "TestRefClass"
attr(,"package")
[1] ".GlobalEnv"
However, a look at the attributes of the class generator object reveals it:
> attributes(TestRefClass)
[... omitted ...]
Reference Superclasses:
"envRefClass"
[... omitted ...]
That's why the dispatch works
R6 Classes
When you would like to a similar thing for R6 classes, things don't seem to be straight forward even though they initially appear so (compared to Reference Classes):
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setMethod("foo", c(x = "R6"),
definition = function(x) {
"I'm the method for `R6`"
})
> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6"’
By "appearing straight forward" I mean that class()
actually suggests that all R6 classes inherit from class R6
that could be used as superclass for method dispatch:
class(TestR6$new())
[1] "TestR6" "R6"
The help page of R6Class()
actually reveals that class R6
is merely added as an informal S3 class as long as class = TRUE
. That's also why there is a warning when trying to define a S4 method for this class.
So then this basically leaves us with two possible options/workarounds:
- Turn class
R6
into a formal class viasetOldClass()
- Have all instances of R6 classes inherit from some other superclass, say,
.R6
Ad 1)
setOldClass("R6")
> isClass("R6")
[1] TRUE
This works when hacking away in an S3 style at the class table/graph:
dummy <- structure("something", class = "R6")
> foo(dummy)
[1] "I'm the method for `R6`"
However, it fails for actual R6 class instances:
> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6"’
Ad 2)
.R6 <- R6Class(".R6")
TestR6_2 <- R6Class("TestR6_2", inherit = .R6, public = list(.x = "numeric"))
setMethod("foo", c(x = ".R6"),
definition = function(x) {
"I'm the method for `.R6`"
})
> try(foo(x = TestR6_2$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6_2"’
Conclusion
While approach 1 sort operates in a "grey area" to make S3 and S4 somewhat compatible, approach 2 seems like a perfectly valid "pure S4" solution that IMO should work. The fact that it's not brought me to raising the question if there exists an inconsistency in the implementation of R6 classes with respect to the interaction of informal/formal classes and method dispatch in R.
回答1:
Courtesy of Hadley Wickham I found out that setOldClass()
in fact solves the problem when including the inheritance structure:
require("R6")
setOldClass(c("TestR6", "R6"))
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setGeneric("foo", signature = "x",
def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "R6"),
definition = function(x) {
"I'm the method for `R6`"
})
try(foo(x = TestR6$new()))
However, AFAICT, this implies that for your packages, you need to make sure that setOldClass()
is called in that way for all of your R6 classes for which you would like your S4 methods to work.
This could be done by bundling these calls in function .onLoad()
or .onAttach()
(see here):
.onLoad <- function(libname, pkgname) {
setOldClass(c("TestR6_1", "R6"))
setOldClass(c("TestR6_2", "R6"))
setOldClass(c("TestR6_3", "R6"))
}
This is supposing that you have defined three R6 classes (TestR6_1
through TestR6_3
)
来源:https://stackoverflow.com/questions/27384772/inconsistency-of-s4-dispatch-behavior-for-r6-classes