R6 icon indicating copy to clipboard operation
R6 copied to clipboard

Support for heritable classmethods

Open alistaire47 opened this issue 2 years ago • 0 comments

In Python, classmethods are heritable methods that can be called from the class itself MyClass.class_method() (not just from an instance of that class MyClass().normal_method()) like a staticmethod, but which also has access to cls, the class definition. This is particularly helpful for alternate ways of instantiating a class, like pd.DataFrame.from_dict().

In R6, all methods are only available after instantiation, unless they're explicitly added to the environment after class creation like

MyClass <- R6Class(...)
MyClass$my_static_method <- function() ...

but that is effectively a non-heritable (it will not exist on any child classes) static method, not a class method that has access to the class itself. Since MyClass already exists, you could call it recursively:

MyClass$my_quasi_class_method <- function(...) do.call(MyClass$new, list(...))

...but that moves it further from heritability, since when inherited it would still point to the parent class instead of the current class.

For my own purposes (I needed a set of classes that can be serialized/deserialized to/from JSON), I created a wrapper around R6Class that stores classmethods (all in a static list for now, since any classmethod that does not reference self (really equivalent to cls here) is a staticmethod) in private$static, and then appends them to child classes when applicable:

R6Point1Class <- function(..., static = NULL) {
  Class <- R6::R6Class(...)
  Class$parent_env <- parent.frame()

  full_static <- modifyList(
    Class$get_inherit()$private_fields$static %||% list(),
    static %||% list()
  )
  full_static <- lapply(full_static, function(x) {
    if (!is.function(x)) return(x)
    environment(x) <- Class
    x
  })
  Class$set('private', 'static', full_static)

  for (name in names(Class$private_fields$static)) {
    Class[[name]] <- Class$private_fields$static[[name]]
  }

  Class
}

I'm sure this implementation has bugs and corner cases (at a minimum, it does not play nice with roxygen), but given that class methods are common in Python, maybe there is appetite for adding support directly to R6Class? https://github.com/r-lib/R6/issues/66 looks like a similar proposal for static attributes and methods; this would presumably be similarly implemented, but with an evaluation environment with access to the class definition (presumably as self or class or cls or some other reserved keyword). At a minimum, it seems to me worth a conversation!

alistaire47 avatar Jun 07 '22 18:06 alistaire47