library(later)

Later

Scoped side effects and events for R.

Travis-CI Build Status

This package:

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()))
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.

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:

  1. 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,

  2. A call to emit() can emit any kind of data object; you are not limited to R conditions,

  3. 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'),

  4. 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()

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



kevinushey/later documentation built on May 20, 2019, 9:09 a.m.