Provides modules as an organizational unit for source code. Modules enforce to be more rigorous when defining dependencies and have a local search path. They can be used as a sub unit within packages or in scripts.
From CRAN:
install.packages("modules")
From GitHub:
devtools::install_github("wahani/modules")
The key idea of this package is to provide a unit which is self contained, i.e. has it’s own scope. The main and most reliable infrastructure for such organizational units of source code in the R ecosystem is a package. Compared to a package modules can be considered ad hoc, but - in the sense of an R-package - self contained. Furthermore modules typically consist of one file; in contrast to a package which can wrap an arbitrary number of files.
There are two use cases. First when you use modules to develop scripts, which is subject of this section; And then inside of packages where modules act more like objects, as in object-oriented-programming. Outside of packages modules know only of the base environment, i.e. within a module the base environment is the only package on the search path. Also they are always represented as a list inside R. Thus they can be treated as bags of functions.
In the following examples you will see the function module
to define modules. Typically you do not have to call that function explicitly but instead call use
to load a module into your current session.
library(modules)
m <- module({
boringFunction <- function() cat("boring output")
})
m$boringFunction()
## boring output
Since they are isolated from the .GlobalEnv
the following object hey
can not be found:
hey <- "hey"
m <- module({
isolatedFunction <- function() hey
})
m$isolatedFunction()
## Error in m$isolatedFunction(): object 'hey' not found
If you rely on exported objects of packages you can refer to them explicitly using ::
:
m <- module({
functionWithDep <- function(x) stats::median(x)
})
m$functionWithDep(1:10)
## [1] 5.5
Or you can use import
for attaching single objects or packages and use
for attaching or loading a module:
m <- module({
import(stats, median) # make median from package stats available
functionWithDep <- function(x) median(x)
})
m$functionWithDep(1:10)
## [1] 5.5
m <- module({
import(stats)
functionWithDep <- function(x) median(x)
})
m$functionWithDep(1:10)
## [1] 5.5
It may also be of interest to control which objects are visible for the client. You can do that with the export
function. Note that export accepts regular expressions which are indicated by a leading ‘^’.
m <- module({
export("fun")
.privateFunction <- identity
privateFunction <- identity
fun <- identity
})
names(m)
## [1] "fun"
One example where you may want to have more control of the enclosing environment of a function is when you parallelize your code. First consider the case when a naive implementation fails.
library(parallel)
dependency <- identity
fun <- function(x) dependency(x)
cl <- makeCluster(2)
clusterMap(cl, fun, 1:2)
## Error in checkForRemoteErrors(val): 2 nodes produced errors; first error: could not find function "dependency"
stopCluster(cl)
To make the function fun
self contained we can define it in a module.
m <- module({
dependency <- identity
fun <- function(x) dependency(x)
})
cl <- makeCluster(2)
clusterMap(cl, m$fun, 1:2)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 2
stopCluster(cl)
You can make scripts into modules with as.module
or implicitly when you refer to a file (or directory) in a call to use
. Inside such a script you can use import
and use
in the same way you typically use library
. A major difference is, that library will not only attach the stated package but also all packages in the depends field of that dependency. This is something you have to do manually (explicitly) with import
. Consider the following example where we create a module in a temporary file with its dependencies.
code <- "
import(methods)
import(aoos)
# This is an example where we rely on functions in 'aoos':
list : generic(x) %g% standardGeneric('generic')
generic(x ~ ANY) %m% as.list(x)
"
fileName <- tempfile(fileext = ".R")
writeLines(code, fileName)
Then we can load such a module into this session by the following:
someModule <- use(fileName)
someModule$generic(1:2)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 2
If you want proper documentation for your functions or modules you really want a package. However, there are some simple things you can do for ad-hoc documentation of modules which is to use comments:
m <- module({
fun <- function(x) {
## A function for illustrating documentation
## x (numeric)
x
}
})
There are print methods for modules and functions within modules:
m
## fun:
## function(x)
m$fun
## function(x)
## ## A function for illustrating documentation
## ## x (numeric)
…
S3 method dispatch will not work because of the special search mechanism of UseMethod
. What will work, however, is attaching the module so UseMethod
can find the methods.
m <- module({
generic <- function(x) UseMethod("generic")
generic.numeric <- function(x) cat("method for x ~ numeric")
})
# m$generic(1) # this won't work
use(m, attach = TRUE)
m$generic(1)
## method for x ~ numeric
More reliable is the dispatch in S4. By default the set functions of the methods package have side effects in the top level environment. So you would have to set the appropriate environemnt for the argument ‘where’. ‘aoos’ provides syntactic sugar (S4) and has side effects in the environment in which the constructor functions are called so method dispatch will work without extra work:
m <- module({
import("methods")
import("aoos")
gen(x) %g% cat("default method")
gen(x ~ numeric) %m% cat("method for x ~ numeric")
})
m$gen("Hej")
## default method
m$gen(1)
## method for x ~ numeric
S4 classes (or types in aoos) have side effects in the global environment and S4 classes are searched for in the top level environment. This means that the module is not self-contained because the class definition will be cached somewhere else.
m <- module({
import("methods")
import("aoos")
numeric : NewType() %type% .Object
})
m$NewType(1)
## An object of class "NewType"
## [1] 1