library(later)
Scoped side effects and events for R.
This package:
generalizes on.exit()
to a function defer()
, 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 through emit()
.
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())) in_dir("tests") print(basename(getwd()))
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.
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 call on()
anywhere in a function's body, and any events emitted
later on in any R
code will be handled by that handler,
A call to emit()
can emit any kind of data object; you are not limited
to R
conditions,
A registered handler for an event won't alter control flow (ie, this event
system doesn't have the restarts
mechanism that R
uses 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()
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
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.