r-lib / R6

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

Dynamically create active bindings during initialize #280

Closed mattwarkentin closed 1 year ago

mattwarkentin commented 1 year ago

Hi @wch,

I previously asked a similar question about whether it was possible to generate public fields/methods dynamically during the init call (https://github.com/r-lib/R6/issues/212). I am now wondering if this is possible for active bindings. The use-case is that there are an unknown number of active bindings to be generated (and their names are unknown too, of course) until input data is received during initialization.

Is it possible to generate active bindings dynamically during the initialize() call?

library(R6)

Foo <- R6Class(
  classname = 'Foo',
  public = list(
    initialize = function(data) {
      self$data <- data
      private$create_active_binding(self$data)
    }
  ),
  private = list(
    create_active_binding = function(data) {
      # not sure how to do this...
    }
  )
)
mattwarkentin commented 1 year ago

This is a start, I think. The only other issue I'm facing is that I was hoping that the active binding would be able to access its own name when called, using something like deparse(match.call()[[1]]) but this does not work as it does with normal functions. Any advice? Is it possible for the function called by an active binding to access the name of the object?

library(R6)

Foo <- R6Class(
  classname = 'Foo',
  lock_objects = FALSE,
  public = list(
    initialize = function(data) {
      purrr::map(data, \(x) {
        makeActiveBinding(x, private$fun, self)
      })
    }
  ),
  private = list(
    fun = function(x) {
      return(10)
    }
  )
)

foo <- Foo$new(LETTERS[1:5])

foo
#> <Foo>
#>   Public:
#>     A: active binding
#>     B: active binding
#>     C: active binding
#>     clone: function (deep = FALSE) 
#>     D: active binding
#>     E: active binding
#>     initialize: function (data) 
#>   Private:
#>     fun: function (x)

foo$A
#> [1] 10
mattwarkentin commented 1 year ago

Okay, this is getting close, but the last step is being able to access the name of the active binding. Any advice? I want to be able to call do_this() and do_that() with the name of the active binding.

library(R6)

Foo <- R6Class(
  classname = 'Foo',
  lock_objects = FALSE,
  public = list(
    initialize = function(data) {
      purrr::map(data, \(d) {
        f <- function(x) {
          if (missing(x)) return(private$do_this())
          private$do_that(x)
        }
        makeActiveBinding(d, f, self)
      })
    }
  ),
  private = list(
    do_this = function() 'this works',
    do_that = function(x) print(paste0('that works: ', x))
  )
)

foo <- Foo$new(LETTERS[1:5])

foo
#> <Foo>
#>   Public:
#>     A: active binding
#>     B: active binding
#>     C: active binding
#>     clone: function (deep = FALSE) 
#>     D: active binding
#>     E: active binding
#>     initialize: function (data) 
#>   Private:
#>     do_that: function (x) 
#>     do_this: function ()

foo$A
#> [1] "this works"
foo$A <- 'Z'
#> [1] "that works: Z"