[R] Inconsistency of S4 dispatch behavior for R6 classes

Janko Thyson janko.thyson at gmail.com
Tue Dec 9 18:28:53 CET 2014


Dear list,

as a week has passed now after filing an issue for package R6 (
https://github.com/wch/R6/issues/36), I thought it's okay to go ahead and
ask a bigger audience about their opinion/suggestions for a general
solution and/or "good" workarounds:

Actual questions
----

1. Shouldn't the fact that [*R6*](https://github.com/wch/R6) classes
inherit from (informal S3) class `R6` allow the definition of S4 methods
for signature arguments of that very class?

2. 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*](
https://stat.ethz.ch/R-manual/R-devel/library/methods/html/refClass.html)
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 ...]
     Class Methods:
        "callSuper", "copy", "export", "field", "getClass", "getRefClass",
"import", "initFields",
    "show", "trace", "untrace", "usingMethods"


     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:

1. Turn class `R6` into a formal class via `setOldClass()`
2. 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.

	[[alternative HTML version deleted]]



More information about the R-help mailing list