r-lib / R6

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

Cannot add functions to R6 that was declared without any public functions #51

Closed daattali closed 9 years ago

daattali commented 9 years ago

If I declare an R6 object and try to add a public function later, I get an error

Test <- R6::R6Class(
  "test",

  public = list(
  ),

  private = list(
  )
)

> Test$set("public", "setName", function(s) s)
Error in self[[group]][[name]] <- value : 
  invalid type/length (closure/0) in vector allocation

I seem to have to have at least one public function in the object definition to be able to do this without error

Test <- R6::R6Class(
  "test",

  public = list(
    anything = function(){}
  ),

  private = list(
  )
)

> Test$set("public", "setName", function(s) s)
daattali commented 9 years ago

Same behaviour for private functions - you have to have at least one defined in order to add any later

Tutuchan commented 9 years ago

I noticed the same thing about a month ago, see here.

If you take a look at the source code, in r6_class.R, the public and private fields and methods are initialized this way :

 generator$public_fields   <- get_nonfunctions(public)
 generator$private_fields  <- get_nonfunctions(private)
 generator$public_methods  <- get_functions(public)
 generator$private_methods <- get_functions(private)

where get_functions is declared in utils.R in this way :

get_functions <- function(x) {
    funcs <- vapply(x, is.function, logical(1))
    if (all(!funcs)) return(NULL)
    x[funcs]
  }

So, if there are no functions in public, which is a list, get_functions will return NULL which means the public_methods field of the class will not exist. Then you try to set a new method on this class, take a look at the set code (I'm just pasting the interesting parts) :

generator_funs$set <- function(which = NULL, name = NULL, value, overwrite = FALSE) {
  (...)
  # Find which group this object should go in.
  if (which == "public") {
    group <- if (is.function(value)) "public_methods" else "public_fields"
  } else if (which == "private") {
    group <- if (is.function(value)) "private_methods" else "private_fields"
  } else if (which == "active") {
    if (is.function(value))
      group <- "active"
    else
      stop("Can't add non-function to active")
  }
  (...)
  # Assign in correct group
  self[[group]][[name]] <- value
  (...)
}

In the last line, you'll see that it tries to add the new value to a group that may not actually exist. So I guess for now, you'll have to add at least an element to each group that you think you may need later on. A possible fix would be to have get_functions and get_nonfunctions return an empty list rather than NULL when there are no elements.