using external classes with Shiny, R and futures
Asked Answered
S

1

9

I am trying to put together a framework to get Shiny working asynchronously on a set of classes I have, using futures and possibly promises. I have put together a test project using a set of external modules which mimic my real setup.

Note: I have also tried to implement the exact same call that is throwing the error in this framework: FutureProcessor.R, and the returned error is identical.

Basically, the button click calls a function that instantiates an instance of a class, which then carries out a simple calculation. When run with the first button as a straight process, this works fine. However when I run it using the %<-% assigmment it returns the following error: Warning: Error in getClass: "cTest" is not a defined class

It's clear to me that I am not getting this right! However I am not sure whether what I am trying to do is even possible?

Setup as follows:

Shiny app:

## Load required libraries
pacman::p_load(shiny, here, promises, future)

setwd(here())
source(here("testing.R"))
source(here("TestClass.R"))
plan(multisession)

# Define UI
ui <- fluidPage(

   # Application title
   titlePanel("Test external classes"),

   # Sidebar 
   sidebarLayout(
      sidebarPanel(
         actionButton("clickMe", "I work"),
         actionButton("clickMeToo", "I don't work")
      ),

      # Show a text output
      mainPanel(
         verbatimTextOutput("outputText1"),
         verbatimTextOutput("outputText2")
      )
   )
)

# Define server logic 
server <- function(input, output) {

  myResult <- NULL

   observeEvent(input$clickMe, {

     ## This works:
      myResult <<- testFutures()
      output$outputText1 <- renderText({paste0("test: ", myResult$Item3)})

   })
   observeEvent(input$clickMeToo, {
     ## This works not:
     myResult %<-% {testFutures()}
     output$outputText2 <- renderText({paste0("test: ", myResult$Item3)})
   })
}

# Run the application 
shinyApp(ui = ui, server = server)

My test class:

cTest <- setRefClass("cTest", 
                                     fields=list(

                                       Item1="numeric",
                                       Item2="numeric",
                                       Item3= "numeric"),

                                     methods = list(
                                       Reset = function() {
                                         Item1 <<- 0
                                         Item2 <<- 0
                                         Item3 <<- 0
                                       },
                                       AddUp = function() {
                                         Item3 <<- Item1 + Item2
                                       }
                                     )

My test function:

testFutures <- function() {
  output <- new ("cTest")
  output$Reset()
  output$Item1 <- 3
  output$Item2 <- 4
  output$AddUp()
  return(output)
}
Snuggery answered 19/10, 2018 at 8:35 Comment(0)
M
5

I think there are a few issues with using reference classes in asynchronous futures, as described in A Future for R: Common Issues with Solutions.

The first is around missing globals. Future statically inspects the code before running to figure out what global variables it needs to expose to the R process. When you instantiate a reference class object using new("classname"), the actual class definition isn't known until runtime (when getClass() is called) so future won't know to export it.

A minimal example:

library(future)

plan(multisession)

RefClassTest <- setRefClass("RefClassTest",
  fields = list(x = "numeric"),
  methods = list(get = function() x)
)

result %<-% new("RefClassTest")
result
## Error in getClass(Class, where = topenv(parent.frame())) :
##   "RefClassTest" is not a defined class

A workaround would be to instantiate using the class generator like RefClassTest$new(). However, now you run into an issue exporting the generator since (I guess) it uses external pointers internally. The object isn't quite constructed right.

options(future.globals.onReference = "warning")

result %<-% RefClassTest$new()
## Warning message:
##   In FALSE :
##   Detected a non-exportable reference (‘externalptr’) in one of the globals (‘RefClassTest’ of class ‘refObjectGenerator’) used in the future expression

result
## Prototypical reference class object

result$get()
## Error in result$get() : object 'x' not found

I don't know enough about reference classes to work around both issues, so I would suggest using R6 classes instead. They don't seem to have the same issues as reference classes in future expressions.

R6Test <- R6::R6Class("R6Test",
  public = list(
    x = numeric(0),
    get = function() self$x
  )
)

result %<-% {
  R6Test$new()
}

result
## <R6Test>
##   Public:
##   clone: function (deep = FALSE)
##     get: function ()
##       x:

result$get()
## numeric(0)
Montemayor answered 24/10, 2018 at 1:5 Comment(2)
This is great, thank you. Since I'm using someone else's code with their classes I've never had to look into classes before, so thank you for providing another piece of the puzzle. Lots to learn.Snuggery
Apologies for the delay in awarding the bounty - I didn't realise it did not do that automatically when accepting the answer.Snuggery

© 2022 - 2024 — McMap. All rights reserved.