-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
118 lines (89 loc) · 3.59 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
```{r setup, include=FALSE}
library(later)
```
# Later
Scoped side effects and events for R.
[![Travis-CI Build Status](https://travis-ci.org/kevinushey/later.svg?branch=master)](https://travis-ci.org/kevinushey/later)
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()`.
## 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:
```{r}
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:
```{r}
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.
```{r}
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](https://github.com/jimhester/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