later
later copied to clipboard
Scoped side-effects and events for R.
Later
Scoped side effects and events for R.
This package:
-
generalizes
on.exit()to a functiondefer(), which can attach exit handlers to any currently executing function, and uses that to scope side effects, and -
provides a simple event system, using event handlers registered through
on()/off(), and events fired throughemit().
Scoped Side Effects
The defer() function generalizes on.exit(), allowing a function to perform cleanup work after a parent invoking function has finished execution. For example, the following scope_dir(dir) function can be used to set the working directory for duration of a function's execution:
scope_dir <- function(dir) {
owd <- setwd(dir)
defer_parent(setwd(owd))
}
The scope_dir() function can be called to set the working directory, and restore the working directory when the caller is done. For example:
in_dir <- function(dir) {
scope_dir(dir)
print(basename(getwd()))
}
print(basename(getwd()))
## [1] "later"
in_dir("tests")
## [1] "tests"
print(basename(getwd()))
## [1] "later"
In effect, defer() provides a mechanism for mimicing something like C++ destructors -- you can call a function that registers actions that will be run when the enclosing function has finished execution.
Events
Register an event handler using on("event", <handler>), and fire events with emit("event", <data>). Useful for long-range communication between functions / objects.
These functions are similar to builtin R capabilities: signalCondition() allows you to signal a condition, and withCallingHandlers() can be used to register handlers for signaled conditions (alongside errors, warnings, and otherwise). The main difference between the system in later and in base R:
-
Rather than wrapping the executing expression in
withCallingHandlers(), you can just callon()anywhere in a function's body, and any events emitted later on in anyRcode will be handled by that handler, -
A call to
emit()can emit any kind of data object; you are not limited toRconditions, -
A registered handler for an event won't alter control flow (ie, this event system doesn't have the
restartsmechanism thatRuses for 'long jumps'), -
Handlers are registered in a stack (so the most recently registered handlers will handle an event first); and handlers can call
stop_propagation()to ensure a handler deeper in the stack does not receive the event if desired.
An example to illustrate, with a set of 'workers' that can call emit() when they've completed some work, and a 'manager' that listens for the emitted events from these workers.
set.seed(1)
make_worker <- function(i) {
force(i)
function() {
emit("work", i)
}
}
workers <- lapply(seq_len(5), make_worker)
manager <- function() {
counter <- 0
on("work", function(data) {
cat(sprintf("Received data: '%s'\n", data), sep = "")
counter <<- counter + 1
})
# Run for 2 milliseconds
time <- Sys.time()
while (Sys.time() - time < 0.002) {
worker <- sample(workers, 1)[[1]]
worker()
}
cat(sprintf("Executed %s tasks.\n", counter), sep = "")
}
manager()
## Received data: '4'
## Received data: '2'
## Received data: '3'
## Received data: '5'
## Received data: '5'
## Received data: '5'
## Received data: '5'
## Received data: '4'
## Received data: '2'
## Received data: '4'
## Executed 10 tasks.
Inspiration & Credit
This package is heavily inspired by Jim Hester's withr package.
The main 'trick' that enabled the generalization of on.exit() was provided in a mailing list post here, by Peter Meilstrup: https://stat.ethz.ch/pipermail/r-devel/2013-November/067874.html