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)
}
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With