The mod
package is designed to be used either attached
or unattached to your search path.
The following demonstrations show the package attached:
Define an inline module:
The resulting module contains the objects defined within.
Subset the module to access its objects.
Use with()
to aceess the objects with their bare
names.
Just like a package, a module can be attached to the search path.
The my
module is attached to the search path as
“module:my”.
search()
#> [1] ".GlobalEnv" "module:my" "package:mod"
#> [4] "package:rmarkdown" "package:stats" "package:graphics"
#> [7] "package:grDevices" "package:utils" "package:datasets"
#> [10] "package:methods" "Autoloads" "package:base"
And you can use the objects inside directly, just like those from a package.
Detach the module from the search path when done, if desired.
Use refer()
to “copy” objects from another module. In
the following example, we create a new module my_other
that
uses the objects from my
, which is previsouly defined.
ls(my)
#> [1] "a" "b" "f"
my_other<- module({
refer(my)
c <- 4
d <- 5
f <- function() print("foo")
})
ls(my_other)
#> [1] "a" "b" "c" "d" "f"
my_other$f()
#> [1] "foo"
In addition to its own objects, my_other
module has all
objects from my
, except f
: because
my_other
module also has a f
object, and
replaces the f
from my
module.
We can re-define my_other
and prepend objects from
my
with my. This way, both f
s are
available.
A variable is private if its name starts with
..
.
room_101 <- module({
..diary <- "Dear Diary: I used SPSS today..."
get_diary <- function(){
..diary
}
})
A private variable cannot be seen or touched. There is no way to
directly access ..diary
from the outside, except by a
function defined within the module, get_diary()
. This can
be useful if you want to shield some information from the other users or
programs.
ls(room_101)
#> [1] "get_diary"
room_101$..diary
#> NULL
room_101$get_diary()
#> [1] "Dear Diary: I used SPSS today..."
If using provide()
function to explicitly declair public
variables, all others become private.
room_102 <- module({
provide(open_info, get_classified)
open_info <- "I am a data scientist."
classified_info <- "I can't get the database driver to work."
get_classified <- function(){
classified_info
}
})
ls(room_102)
#> [1] "get_classified" "open_info"
room_102$open_info
#> [1] "I am a data scientist."
room_102$classified_info
#> NULL
room_102$get_classified()
#> [1] "I can't get the database driver to work."
The mod
package provides a require()
function. mod:::require()
works in the same manner as do
base::require()
, but makes a packages available for use in
its containing module only.
Meanwhile, the global search path remain unaffected, not containing
the ggplot2
package:
mod::ule
is simple, lightweight and staright-forward. It
is plain R and contains zero GMO.
Modules live any any other R object.
You do things the R way. For example, acquire()
and
module()
only returns a module object; It is up to you to
put it into your working environemnt with <-
.d
In this example, Bobby gets hungry, Dad complains and calls Mom:
bobby <- mod::ule({
age_now <- 14
favorite_food <- c("hamburger", "pizza", "chow mein")
hungry_for <- function() {
set.seed(Sys.Date())
sample(favorite_food, 1)
}
cry <- function(){
sprintf("I just really really want some %s, now!!!", hungry_for())
}
})
dad <- mod::ule({
refer(bobby, prefix = .)
provide(complain, call_mom)
complain <- function(){
sprintf("When I was %d, I've already earned a living on the dock.",
bobby.age_now)
}
call_mom <- function(){
sprintf("Honey, can you stop by the convenience store and get some %s?",
bobby.hungry_for())
}
})
bobby$cry()
#> [1] "I just really really want some pizza, now!!!"
dad$complain()
#> [1] "When I was 14, I've already earned a living on the dock."
dad$call_mom()
#> [1] "Honey, can you stop by the convenience store and get some pizza?"
It is imperative that mod
be only adopted in simple
cases; no mutable state is allowed in modules. If full-featured OOP is
desired, use R6
. The
user is reminded that OOP, in general, should be used sparingly and with
deliberation.
A module is an environment. This means that every rule that applies to environments, such as copy-by-reference, applies to modules as well.
Some may wonder the choice of terms. Why refer()
and
provide()
? Further, why not import()
and
export()
? This is because we feel import and
export are used too commonly, in both R, and other popular
languages with varying meanings. The popular reticulate
package also uses import()
. To avoid confusion, we decided
to introduce some synonyms. With analogous semantics, refer()
is borrowed from Clojure, while provide()
from Racket; Both languages are R’s close relatives.
A module is locked by default. It is impossible to either change the value of a object or add a new object to a module.
To load and assign to object:
example_module <- acquire(module_path)
ls(example_module)
#> [1] "a" "d" "e"
example_module$a
#> [1] 1
example_module$d()
#> [1] 6
example_module$e(100)
#> [1] 106
To load and attach to search path:
As it could be confusing how to deal with modules and packages, the following clarification is made:
require()
require()
use()
refer()
Depends
or Imports
the mod
package*: Modules cannot be attached to another module’s “seach path”. Use
refer()
instead to make clearly visible objects in the
module context. **: Objects cannot be batch-copied from a module to the
global environment, use use()
to attach the module to the
global search path in order to use them directly.
These two features seek to avoid one very important problem
mod
packages intends to solve: conflicting names.
As aforementioned, the package is designed to be usable both attached and unattached.
If you use the package unattached, you must always qualify the object
name with ::
, such as mod::ule()
, a shorthand
for mod::module()
. However, while inside a module, the
mod
package is always available, so you do not need to use
::
. Note that in the following example,
provide()
inside the module expression is unqualified.
See: