# Sets a variable in a parent environment
value <<-10# Loads into global env by defaultsource("functions.R")
# Modifies the global search listlibrary(dplyr)
# Only if foo is an env, ref class, or R6
foo$bar <-TRUE
NOT side effects (when inside a function)
# Modifying *local* variables
value <-10# Creating most kinds of objectslist(a =1, b =2)
# Data frames are pass-by-value in R so this is OK
dataset <-dataset %>%filter(count >3)
# Most calculations
a +1summary(pressure)
lm(speed ~dist, data = cars)
predict(wfit, interval ="prediction")
Ehhh… Not side effects
# Reading from diskreadLines("data.csv")
# Making HTTP GET requestshttr.GET("https://api.example.com/data.json")
# Reading global variables
.Random.seed
# Modifying the random seed... ehhhhhh...runif(10)
If executing your function/expression leaves the state of the world a little different than before it executed, it has side effects.
But if “what happens in func, stays in func” (besides the return value), then it doesn’t have side effects.
Side effect quiz
For each function, write Yes if it has side effects, and No if not.
function() {
# Create temp file, and delete when function exits
filePath <-tempfile(fileext =".png")
on.exit(file.unlink(filePath))
# Plot to the temp file as PNG imagepng(filePath); plot(cars); dev.off()
# Return the contents of the temp filereadBin(filePath, "raw", n =file.info(filePath)$size)
}
Answers
No
Yes
Yes
No
No
Yes
Mostly no
Side effects make code harder to reason about, since order of execution of different side-effecty functions can matter (in non-obvious ways).
But we still need them. Without side effects, our programs are useless! (If a program executes but has no observable interactions with the world, you may as well have not executed it at all!)
Reactive programming
Ladder of Enlightenment
Made it halfway through the tutorial. Has used output and input.
Made it entirely through the tutorial. Has used reactive expressions (reactive()).
Has used observe() and/or observeEvent(). Has written reactive expressions that depend on other reactive expressions. Has used isolate() properly.
Can say confidently when to use reactive() vs. observe(). Has used invalidateLater.
Writes higher-order reactives (functions that have reactive expressions as input parameters and return values).
Understands that reactive expressions are monads.
Exercise 0
Open Exercise_00.R and complete the server function. Make the plot output show a simple plot of the first nrows rows of a built-in dataset.
DOESN’T mean: “Go update the output "plot1" with the result of this code.”
DOES mean: “This code is the recipe that should be used to update the output "plot1".”
Takeaway
Know the difference between telling Shiny to do something, and telling Shiny how to do something.
Reactive expressions
Expressions that are reactive(obviously)
Expression: Code that produces a value
Reactive:Detects changes in anything reactive it reads
function(input, output, session) {
# When input$min_size or input$max_size change, large_diamonds# will be notified about it.
large_diamonds <-reactive({
diamonds %>%
filter(carat >=input$min_size) %>%
filter(carat <input$max_size)
})
# If that happens, large_diamonds will notify output$table.
output$table <-renderTable({
large_diamonds() %>%select(carat, price)
})
... continued ...
# Reactive expressions can use other reactive expressions.
mean_price <-reactive({
mean(large_diamonds()$price)
})
# large_diamonds and mean_price will both notify output$message# of changes they detect.
output$message <-renderText({
paste0(nrow(large_diamonds()), " diamonds in that range, ",
"with an average price of $", mean_price())
})
}
Prefer using reactive expressions to model calculations, over using observers to set (reactive) variables.
Exercise 2
Open up the file Exercise_02.R.
This is a working app–you can go ahead and run it. You choose variables from the iris (yawn) data set, and on various tabs it shows information about the selected variables and fits a linear model.
The problem right now, is that each of the four outputs contains copied-and-pasted logic for selecting out your chosen variables, and for building the model. Can you refactor the code so it’s more maintainable and efficient?
# Don't do this!# Introduce reactive value for each calculated value
values <-reactiveValues(selected =NULL, model =NULL)
# Use observers to keep the values up-to-dateobserve({
values$selected <-iris[, c(input$xcol, input$ycol)]
})
observe({
values$model <-lm(paste(input$ycol, "~", input$xcol), values$selected)
})
Takeaway
Seriously, prefer using reactive expressions to model calculations, over using observers to set (reactive) variables.
Observers
Observers are blocks of code that perform actions.
They’re executed in response to changing reactive values/expressions.
They don’t return a value.
observe({
cat("The value of input$x is now ", input$x, "\n")
})
Observers come in two flavors
Implicit: Depend on all reactive values/expressions encountered during execution. observe({...})
Explicit: Just depend on specific reactive value/expression; ignore all others. (Also known as “event handler”.) observeEvent(eventExpr, {...})
function(input, output, session) {
# Executes immediately, and repeats whenever input$x changes.observe({
cat("The value of input$x is now ", input$x, "\n")
})
# Only executes when input$upload_button is pushed. Any reactive# values/expressions encountered in the code block are treated# as non-reactive values/expressions.observeEvent(input$upload_button, {
httr::POST(server_url, jsonlite::toJSON(dataset()))
})
}
Exercise 3
Open Exercise_03.R.
Add server logic so that when the input$save button is pressed, the data is saved to a CSV file called "data.csv" in the current directory.
You have 5 minutes!
Solution
# Use observeEvent to tell Shiny what action to take# when input$save is clicked.observeEvent(input$save, {
write.csv(df(), "data.csv")
})
Reactive expressions vs. observers
reactive()
It can be called and returns a value, like a function. Either the last expression, or return().
It’s lazy. It doesn’t execute its code until somebody calls it (even if its reactive dependencies have changed). Also like a function.
It’s cached. The first time it’s called, it executes the code and saves the resulting value. Subsequent calls can skip the execution and just return the value.
It’s reactive. It is notified when its dependencies change. When that happens, it clears its cache and notifies it dependents.
function(input, output, session) {
reactive({
# This code will never execute!cat("The value of input$x is now ", input$x, "\n")
})
}
The fact that reactive expressions are lazy and cached, is critical.
It’s hard to reason about when reactive expressions will execute their code—or whether they will be executed at all.
All Shiny guarantees is that when you ask a reactive expression for an answer, you get an up-to-date one.
observe() / observeEvent()
It can’t be called and doesn’t return a value. The value of the last expression will be thrown away, as will values passed to return().
It’s eager. When its dependencies change, it executes right away.
(Since it can’t be called and doesn’t have a return value, there’s no notion of caching that applies here.)
It’s reactive. It is notified when its dependencies change, and when that happens it executes (not right at that instant, but ASAP).
reactive()
observe()
Callable
Not callable
Returns a value
No return value
Lazy
Eager
Cached
N/A
reactive() is for calculating values, without side effects.
observe() is for performing actions, with side effects.
A calculation is a block of code where you don’t care about whether the code actually executes—you just want the answer. Safe for caching. Use reactive().
An action is where you care very much that the code executes, and there is no answer (return value), only side effects. Use observe()/observeEvent().
(What if you want both an answer AND you want the code to execute? Refactor into two code chunks–separate the calculation from the action.)
reactive()
observe()
Purpose
Calculations
Actions
Side effects?
Forbidden
Allowed
An easy way to remember
Keep your side effects Outside of your reactives Or I will kill you
—Joe Cheng
Takeaway
Use reactive expressions for calculations (no side effects). Use observers for actions (side effects).
Reactive values
A reactiveValues object is like an environment object or a named list: it stores name/value pairs. You get and set values using $ or [[name]].
Modify the server function so that when the “rnorm” button is clicked, the plot shows a new batch of rnorm(100) values. When “runif” button is clicked, the plot should show a new batch of runif(100).