r-lib / R6

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

Using function factories #114

Closed Tutuchan closed 7 years ago

Tutuchan commented 7 years ago

I'm tryring to define methods in a R6 class based on a function factory and I encounter errors. Here is a reproducible example.

power <- function(exponent) {
  function(x) {
    x ^ exponent
  }
}

A <- R6::R6Class(
  "A",
  public = list(
    square = power(2)
  )
)

test <- A$new()

test$square(4)
#> Error in test$square(4): object 'exponent' not found

Furthermore, I'm doing this in a package. The function factory is defined in aaa.R and my class in class.R and trying to install() or document() the package leads to

Error in all_named(public) (from class.R#108) : could not find function "power"

Maybe I'm missing something but do you have any idea how to make this work ?

wch commented 7 years ago

The reason that the function factory doesn't work is because it generates a closure: the function that's returned by power also has an enclosing environment that contains the value of exponent. However, when the R6 object is instantiated by A$new(), it copies the function but reassigns the function's environment -- that's how methods in R6 objects get scoped to the object. The reassigned environment also causes it to lose the enclosing environment with exponent.

If you want something like this to work, you'd have to make a version of power that, instead of simply using an enclosing environment to hold the value of exponent, would actually alter the body of the function.

See these for more:

As for the "could not find function" problem, I'm not sure offhand why that's happening, as long as aaa.r is getting loaded before class.R. Perhaps the collate order in DESCRIPTION telling it to do something different?

Tutuchan commented 7 years ago

Thanks for the explanation, that makes a lot of sense. I'll try out the examples you provided and report back.

For the 2nd problem, I don't really know either, my collate order is the default one so it should work. I'll take another look later.

Tutuchan commented 7 years ago

Using pryr::unenclose does the job very simply:

power <- function(exponent) {
  function(x) {
    x ^ exponent
  }
}

A <- R6::R6Class(
  "A",
  public = list(
    square = power(2),
    usquare = pryr::unenclose(power(2))
  )
)

test <- A$new()

test$square(4)
#> Error in test$square(4): object 'exponent' not found
test$usquare(4)
#> [1] 16

For the second problem, it indeed comes from the collate directive because some of my files starts with uppercase letters so they are loaded before aaa.R.

Thanks again for pointing me in the right direction.