r-lib / R6

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

Proper way to implement S3 dispatch on R6 classes #42

Closed daattali closed 9 years ago

daattali commented 9 years ago

Crossposting from SO as I haven't received any answers there, I hope that's ok.

TL;DR: While it's not difficult to see how to implement S3 methods for R6 classes, I could not find examples in the documentation that officially show the proper way to do this. Also, I wanted to know if it is discouraged to implement S3 functions and instead the equivalent R6 functions should be encouraged to use? ie. does it matter if we do as.list(myR6) vs myR6$as.list()

Below is a copy-n-paste from the SO post:


I have an R6 class and I want to add an S3 method for it. The documentation I found mentioned briefly that in order to use S3 dispatch on R6 you must have class = TRUE, but I couldn't find an example of how it should be done.

I did see empirically that simply writing an S3 method in the form s3generic.r6class worked, but I wanted to know if that is indeed to right way to write an S3 method for R6.

For example, say I have an R6 class that enhances a list

library(R6)

R6list <- R6Class(
  "R6list",
  public = list(
    orig = NULL,
    initialize = function(x) {
      self$orig <- x
    }
  )
)

Question 1

Naturally, I want to provide a method for obtaining the underlying list, so I wanted to add an as.list method. Is it standard to add both an S3 generic AND a as.list public function inside the class? My intuitive answer is to add both.

R6list <- R6Class(
  "R6list",
  public = list(
    orig = NULL,
    initialize = function(x) {
      self$orig <- x
    },
    as.list = function() {
      self$orig
    }
  )
)

as.list.R6list <- function(x, ...) {
  x$as.list()
}

So that now if I have an object mylist <- R6list$new(as.list(letters[1:5])) I can either call as.list(mylist) or mylist$as.list(). Is one of those preferred over the other?

Question 2
Is there anything special about writing an S3 method for R6 classes, or is what I wrote above sufficient and the correct way? I wasn't sure if the S3 method has to be written outside of the class definition, or if R6 somehow provides a way to write S3 methods within it so that all the code relating to the class is localized.

wch commented 9 years ago

Good questions.

Answer 1: There isn't a generally preferred way of calling it -- at least, not yet. If you're using an R6 object, you may prefer to call mylist$as.list(), just to be consistent with calling other methods on the object. On the other hand, you may prefer to call as.list(mylist) if you want to be consistent with other R objects. The latter may be appropriate especially if you're calling as.list() in a function, and the input could be many types, not just R6list.

Answer 2: You wrote it correctly. The S3 method is defined outside of the class.