-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmigration.rmd
326 lines (244 loc) · 10.2 KB
/
migration.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
---
title: "Migration guide"
author: Konrad Rudolph
date: "`r Sys.Date()`"
output:
rmarkdown::html_vignette:
toc: true
md_document:
variant: gfm
vignette: >
%\VignetteEngine{knitr::rmarkdown}
%\VignetteIndexEntry{Migration guide}
%\VignetteEncoding{UTF-8}
---
## ‘modules’ “v1.0” == ‘box’
‘box’ is the spiritual successor of the [‘modules’ package][modules], which will
forever remain at version 0.9.*x*. However, the API of ‘box’ intentionally
breaks backwards compatibility, and module code written for use with ‘modules’
will no longer work with ‘box’.
The following guide is intended to ease migration to ‘box’. **Migration is
strongly recommended**, since the authors believe that ‘modules’ had conceptual
shortcomings that have been fixed in ‘box’, and to keep the ecosystem
consolidated. For the time being, ‘modules’ will receive support (only) in the
form of critical bug fixes.
## Importing modules and packages
### General syntax
With the ‘modules’ package, modules and packages were imported via
```{r eval = FALSE}
library(modules)
# …
modname = import('prefix/modname')
pkgname = import_package('pkgname')
```
With ‘box’, modules and packages are imported via [`box::use`][box::use]:
```{r eval = FALSE}
box::use(prefix/modname)
box::use(pkgname)
```
Notably, module and package names in a use declaration in ‘box’ are unquoted,
unevaluated expressions; and **the ‘box’ package is never loaded via
`library`**. In fact, `library(box)` raises an error.
Furthermore, with ‘box’, module names *must* be fully qualified. The equivalent
of the ‘modules’ code `mod = import('modname')` no longer exists. To import a
*local* module without namespace prefix, instead use
```{r eval = FALSE}
box::use(./modname)
```
`box::use` has no return value. Instead, it automatically creates an alias with
the module/package name in the calling scope *if no names are attached* (see
below). To override the alias name, specify it as as a named argument:
```{r eval = FALSE}
box::use(mod_alias = prefix/modname)
```
Unlike with `modules::import`, `box::use` allows (and encourages) multiple use
declarations at once:
```{r eval = FALSE}
box::use(
prefix/mod1,
mod = prefix/mod2,
pkg
)
```
### Attaching names
In ‘modules’, the `attach` and `attach_operators` parameters controlled if and
which names were attached. In ‘box’, attachment is controlled via attach list
declarations:
```{r eval = FALSE}
# ‘modules’:
import('prefix/mod', attach = c('name1', 'name2'))
# ‘box’:
box::use(prefix/mod[name1, name2])
```
Wildcard attach lists are also supported:
```{r eval = FALSE}
# ‘modules’:
import('prefix/mod', attach = TRUE)
# ‘box’:
box::use(prefix/mod[...])
```
To introduce a module/package alias when attaching names, specify an alias name:
```{r eval = FALSE}
# ‘modules’:
mod = import('prefix/mod', attach = c('name1', 'name2'))
# ‘box’:
box::use(mod = prefix/mod[name1, name2])
```
‘box’ also allows declaring aliases for attached names; this feature did not
exist in ‘modules’:
```{r eval = FALSE}
# Declare alias for one name, attach other name unchanged:
box::use(prefix/mod[name_alias = name1, name2])
# Declare alias for two names, attach all other exported names unchanged:
box::use(prefix/mod[name_alias1 = name1, name_alias2 = name2, ...])
```
The `attach_operators` option from ‘modules’ has been dropped. If users require
operators, they need to explicitly attach them when using ‘box’.
### Loading documentation
The `doc` option from `modules::import` has been dropped without replacement.
‘box’ loads documentation lazily only when it is requested via `box::help` (see
below).
## Executing code during module loading
The ‘modules’ package treated modules as regular R source code files: upon
importing them, the entire code inside a module file was executed. ‘box’
conceptually no longer does this. It regards module source code as
*declarative*: the module source code defines a number of names to be exported.
However, code with side-effects on the module file level is no longer guaranteed
to execute.
Instead, ‘box’ introduces [module event hooks][hooks], in particular `.on_load`,
which is a function that gets executed whenever a module is first loaded inside
an R session.
## Changed options
In ‘modules’, the module search path was set via `options('import.path')`. In
‘box’, use `options('box.path')` instead.
The ‘modules’ options `options('import.attach')` and `options('warn.conflicts')`
no longer exist. In particular, ‘box’ no longer warns of name conflicts when
attaching names. Instead, it encourages consciously choosing which names to
attach.
## Changed function names
The ‘modules’ function `module_file` is now called [`box::file`][box::file]. Its
semantics have also changed: it no longer cares whether files relative to the
module exist or not; it merely constructs appropriate path strings.
The ‘module’ function `module_name` is now called [`box::name`][box::name]. It
no longer has any arguments.
## Exporting names from modules
With the ‘modules’ packages, module source files exported all non-hidden names;
that is, all names that didn’t start with a dot (`.`).
‘box’ makes exporting explicit. By default, *no* names are exported from a
module, unless they are marked with the directive comment `#' @export`:
```{r eval = FALSE}
# This function is not exported:
f1 = function () {}
# This nested module is not exported
box::use(./nested1)
# This function is exported:
#' @export
f2 = function () {}
# This nested module is exported
#' @export
box::use(./nested2)
```
`@export` directives that decorate `box::use` declarations apply to *all* names
declared in it:
```{r eval = FALSE}
#' @export
box::use(
pkg_alias = pkg,
prefix/mod,
prefix/mod2[a, b, c]
)
```
The above code will export the names `pkg_alias`, `mod`, `a`, `b` and `c`. This
replaces the function `export_submodule` from ‘modules’, which no longer exists.
There’s one exception to this: if a module contains *no* declared export, ‘box’
assumes that it is a plain R script, and treats it as a *legacy module*. This
causes ‘box’ to revert to the export behaviour of ‘modules’. To suppress this
behaviour (that is, to create a module which explicitly doesn’t export any
names), module authors can add the following declaration to their module source
code, which explicitly declares that the source file is to be treated as a
module with no exports:
```{r eval = FALSE}
box::export()
```
## Accessing default packages
In ‘modules’, module source code “sees” the package search path at the time it
is loaded. That is, modules have implicit access to all names in packages that
were attached when the module was loaded. In particular, this means that legacy
modules could access names in the default packages (in non-interactive sessions,
that’s typically ‘datasets’, ‘utils’, ‘grDevices’, ‘graphics’ and ‘stats’; in
interactive sessions, the package ‘methods’ is added).
‘box’ modules no longer attach *any* packages by default, except ‘base’. If a
module needs to use functions from those other packages, it needs to declare
them explicitly (e.g. via `box::use(stats)`). If users want to import all
default R packages, they can import the module `r/core` for this purpose. The
following declaration approximates the behaviour of ‘modules’:
```{r eval = FALSE}
box::use(r/core[...])
```
## Loading parents of nested modules
Consider the following file hierarchy defining a nested module:
<pre><code><b>a</b>
├─ <i>__init__.r</i>
╰─ <b>b</b>
├─ <i>__init__.r</i>
╰─ <i><b>c</b>.r</i></code></pre>
In ‘modules’, the declaration `import(a/b/c)` would import the module `c`, but
this would first execute the code of the modules `a` and `a/b`. In other words
it would source the files `a/__init__.r`, `a/b/__init__.r`, and `a/b/c.r`, in
this order.
‘box’ no longer loads the full module hierarchy: `box::use(a/b/c)` loads only
the module defined by `a/b/c.r`. Likewise, `box::use(a/b)` does *not*
automatically load `a/b/c.r` (but the same behaviour was already present in
‘modules’).
## Displaying documentation
‘modules’ overrode the `help` function and the `?` operator to allow displaying
module documentation. Since ‘box’ is no longer attached, these functions no
longer display module documentation. Instead, the documentation of anything
imported via ‘box’ (both modules and packages!) can be queried via
[`box::help`][box::help].
Unlike ‘modules’, ‘box’ also supports displaying the documentation of nested
module names, e.g. `box::help(a$b$c)`.
## Cyclic imports
Cyclic/circular imports are supported by both ‘modules’ and ‘box’. However, the
level of support differs. The details are complicated, and it is generally
recommended to avoid cyclic modules. However, there are some situations where
circularity in the dependencies makes sense, and ‘box’ strives to make this
work, where technically possible.
For instance, consider the following (nonsensical) working, circular definition
of the functions `even` and `odd`, which determine whether a non-negative
integer is even or odd:
* `even.r`:
```{r eval = FALSE}
box::use(./odd[...])
#' @export
even = function (n) {
n == 0L || ! odd(n)
}
```
* `odd.r`:
```{r eval = FALSE}
box::use(./even[...])
#' @export
odd = function (n) {
n != 0L && even(n - 1L)
}
```
These modules compute `even` in terms of `odd`, and vice-versa. Yet ‘box’ has no
trouble importing and using both these modules. However, this no longer works
once we attempt to export imported submodules themselves. That is, the following
version of `odd.r` would cause an error:
* `odd.r`:
```{r eval = FALSE}
#' @export
box::use(./even[...])
#' @export
odd = function (n) {
n != 0L && even(n - 1L)
}
```
[modules]: https://github.com/klmr/modules
[box::use]: https://klmr.me/box/reference/use.html
[hooks]: https://klmr.me/box/reference/mod-hooks.html
[box::file]: https://klmr.me/box/reference/file.html
[box::name]: https://klmr.me/box/reference/name.html
[box::help]: https://klmr.me/box/reference/help.html