‘box’ is the spiritual successor of the ‘modules’ package, 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.
With the ‘modules’ package, modules and packages were imported via
library(modules)
# …
= import('prefix/modname')
modname = import_package('pkgname') pkgname
With ‘box’, modules and packages are imported via box::use()
:
::use(prefix/modname)
box::use(pkgname) box
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
::use(./modname) box
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:
::use(mod_alias = prefix/modname) box
Unlike with modules::import()
, box::use()
allows (and encourages) multiple use declarations at once:
::use(
box/mod1,
prefixmod = prefix/mod2,
pkg )
In ‘modules’, the attach
and attach_operators
parameters controlled if and which names were attached. In ‘box’, attachment is controlled via attach list declarations:
# ‘modules’:
import('prefix/mod', attach = c('name1', 'name2'))
# ‘box’:
::use(prefix/mod[name1, name2]) box
Wildcard attach lists are also supported:
# ‘modules’:
import('prefix/mod', attach = TRUE)
# ‘box’:
::use(prefix/mod[...]) box
To introduce a module/package alias when attaching names, specify an alias name:
# ‘modules’:
= import('prefix/mod', attach = c('name1', 'name2'))
mod
# ‘box’:
::use(mod = prefix/mod[name1, name2]) box
‘box’ also allows declaring aliases for attached names; this feature did not exist in ‘modules’:
# Declare alias for one name, attach other name unchanged:
::use(prefix/mod[name_alias = name1, name2])
box
# Declare alias for two names, attach all other exported names unchanged:
::use(prefix/mod[name_alias1 = name1, name_alias2 = name2, ...]) box
The attach_operators
option from ‘modules’ has been dropped. If users require operators, they need to explicitly attach them when using ‘box’.
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).
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, in particular .on_load()
, which is a function that gets executed whenever a module is first loaded inside an R session.
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.
The ‘modules’ function module_file
is now called 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()
. It no longer has any arguments.
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
:
# This function is not exported:
= function () {}
f1
# This nested module is not exported
::use(./nested1)
box
# This function is exported:
#' @export
= function () {}
f2
# This nested module is exported
#' @export
::use(./nested2) box
@export
directives that decorate box::use()
declarations apply to all names declared in it:
#' @export
::use(
boxpkg_alias = pkg,
/mod,
prefix/mod2[a, b, c]
prefix )
The above code will export the names pkg_alias
, mod
, a
, b
and c
.
Consider the following file hierarchy defining a nested module:
a
├─ __init__.r
╰─ b
├─ __init__.r
╰─ c.r
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’).
‘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()
.
Unlike ‘modules’, ‘box’ also supports displaying the documentation of nested module names, e.g. box::help(a$b$c)
.
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
:
::use(./odd[...])
box
#' @export
= function (n) {
even == 0L || ! odd(n)
n }
odd.r
:
::use(./even[...])
box
#' @export
= function (n) {
odd != 0L && even(n - 1L)
n }
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
:
#' @export
::use(./even[...])
box
#' @export
= function (n) {
odd != 0L && even(n - 1L)
n }