r-lib / R6

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

Question about "`inherit` must be a R6ClassGenerator", Rook #221

Closed evanbiederstedt closed 3 years ago

evanbiederstedt commented 3 years ago

Hi there

I probably shouldn't write this issue, as it's not a problem with R6 (as far as I can tell), but something with my conceptual understanding. The maintainers of this package are probably in the best position to explain this issue. If this issue is a nuisance, please close :)

Rook is a pretty canonical R package. There is normally a Middleware Class I use, which builds Rook Middleware applications: https://rdrr.io/cran/Rook/man/Middleware-class.html

Here's the original code:

setRefClass(
    'FooBar',
    contains = 'Middleware',
    methods = list(
        initialize = function(...){
            # app to defer to.
            callSuper(app=App$new(function(env){
                res <- Response$new()
                res$write("<h1>I'm the deferred app.</h1>")
                res$finish()
            }))
        },
        call = function(env){
            req <- Request$new(env)
            res <- Response$new()
            if (length(grep('foo',req$path_info()))){
                res$write("<h1>I'm the middleware app.</h1>")        
                return(res$finish())
            } else {
                app$call(env)
            }
        }
    )
)
s <- Rhttpd$new()
s$start(quiet=TRUE)
s$add(name="middle",app=getRefClass('FooBar')$new())
s$browse('middle') # Opens a browser window to the app.

I wanted to translate the reference class into an R6 class. I did this as following:

library(R6)
library(Rook)

Foobar = R6::R6Class('FooBar',
    inherit = Rook::Middleware,
    public = list(
        initialize = function(...){
            # app to defer to.
            callSuper(app=App$new(function(env){
                res <- Response$new()
                res$write("<h1>I'm the deferred app.</h1>")
                res$finish()
            }))
        },
        call = function(env){
            req <- Request$new(env)
            res <- Response$new()
            if (length(grep('foo',req$path_info()))){
                res$write("<h1>I'm the middleware app.</h1>")        
                return(res$finish())
            } else {
                app$call(env)
            }
        }
    )
)

s <- Rhttpd$new()
s$start(quiet=TRUE)
s$add(name="middle",app=Foobar$new())

The error I have is:

> s$add(name="middle",app=Foobar$new())
Error in Foobar$new() : `inherit` must be a R6ClassGenerator.

My questions are roughly: (1) Because this Middleware class is written as a reference class, is it impossible to call it within R6 as a superclass as the above show? (2) Is there a way to provide an R6 equivalent to the above?

Thank you for the help---as I say, I realize this is somewhat out of the scope of an issue for R6...

gaborcsardi commented 3 years ago

(1) Because this Middleware class is written as a reference class, is it impossible to call it within R6 as a superclass as the above show?

Correct.

(2) Is there a way to provide an R6 equivalent to the above?

I think you would need to rewrite all Rook classes as R6 classes.

evanbiederstedt commented 3 years ago

Hi @gaborcsardi

Thank you for the help---it's much appreciated.

I think you would need to rewrite all Rook classes as R6 classes.

Darn. Well, that answers what I wanted to know...

Any other options available?

wch commented 3 years ago

Is there any particular reason to not keep using RefClasses?

If you really want to use R6, I suppose you could create a class that explicitly wraps a RefClass object, as in:

FooBar <- R6::R6Class('FooBar',
  public = list(
    superObj = NULL,
    initialize = function() {
      superObj <- SuperClass$new()
    },
    # "inherit" a method from SuperClass
    otherFunction = function(x) {
      superObj$otherFunction(x)
    },
    # Create a new method or "override" it from SuperClass
    functionA = function(x) {
      x + 1
    }
  )
)

This will be somewhat limited because it can't access stuff that's internal to the superclass. It also adds another layer of complexity for little gain.

evanbiederstedt commented 3 years ago

Hi @wch

I appreciate the explanation.

Is there any particular reason to not keep using RefClasses?

Well, I just find R6 classes easier to use and document. I wanted to port an older reference class (with many functions) to R6, but ran into this issue `inherit must be a R6ClassGenerator.`