Reactive within R6class throws .dependents not found error


#1

When using a reactive within an R6 class the following error is thrown: “Error in Test$new()$test() : object ‘.dependents’ not found”

Why is this and is there a way around?

library(shiny)
library(R6)

Test <- R6Class(
  classname = "Test",
  public = list(
    test = reactive({
      1
    })
  )
)

isolate(Test$new()$test())

Error in Test$new()$test() : object '.dependents' not found

#2

Good question for @winston? :slight_smile:

In the meantime I’ll try an answer. Reactive expressions are implemented as R6 objects underneath, and reactive() really just returns a method on this R6 object. When you transplant this method to a new R6 class, the method receives a new enclosing environment and can no longer access its original object. This explains why:

Implementation note: The external face of an R6 object is basically an environment with the public members in it. This is also known as the public environment. An R6 object’s methods have a separate enclosing environment which, roughly speaking, is the environment they “run in”. This is where self binding is found, and it is simply a reference back to public environment.

https://cran.r-project.org/web/packages/R6/vignettes/Introduction.html#basics

A simplified example might help.

ClassA <- R6::R6Class(
  "ClassA",
  portable = FALSE,
  public = list(
    x = 1,
    test = function() {
      x
    }
  )
)

a <- ClassA$new()

environment(a$test)
#> <ClassA>
#>   Public:
#>     clone: function (deep = FALSE)
#>       self: ClassA, R6
#>       test: function ()
#>       x: 1

a$test()
#> [1] 1
# "transplant" a$test to a new R6 class
Test <- R6::R6Class(
  "Test",
  public = list(
    test = a$test
  )
)

t <- Test$new()

# the transplanted a$test runs in a different environment
# (the enclosing environment of the new object), and can no longer access
# fields on the original object
environment(t$test)
#> <environment: 0x0000000016d8a7a8>

identical(environment(t$test), t$.__enclos_env__)
#> [1] TRUE

t$test()
#> Error in t$test() : object 'x' not found

One way around this is to set the field in the initialize method instead, which is probably what you want to do with reference objects anyway.

https://cran.r-project.org/web/packages/R6/vignettes/Introduction.html#fields-containing-reference-objects

Test <- R6Class(
  classname = "Test",
  public = list(
    test = NULL,
    initialize = function() {
      self$test <- reactive({
        2
      })
    }
  )
)

isolate(Test$new()$test())
#> [1] 2

#3

The reason this happens is that the object that reactive() returns is a special kind of function, and so R6 thinks it is a method. When you instantiate the object, it copies the function and changes its environment, and that’s what leads to the error you’re seeing.

One more detail: the object returned by reactive() also has some extra stuff attached to it as an attribute. This includes an R6 object of class Observable, as you can see:

> str( reactive({1}) )
function ()  
 - attr(*, "observable")=Classes 'Observable', 'R6' reactive({     1 }) 
 - attr(*, "class")= chr [1:2] "reactiveExpr" "reactive"

It’s generally not a good idea to assign an object with reference semantics as a field, as explained here.

@greg’s solution is a good one: assign the field in the initialize() method.