Introduction to Modules

Use

The mod package is designed to be used either attached or unattached to your search path.

The following demonstrations show the package attached:

require(mod)
#> Loading required package: mod
#> 
#> Attaching package: 'mod'
#> The following objects are masked from 'package:base':
#> 
#>     drop, use

Examples

Define an inline module:

my <- module({
        a <- 1
        b <- 2
        f <- function(x, y) x + y
})

The resulting module contains the objects defined within.

ls(my)
#> [1] "a" "b" "f"

Subset the module to access its objects.

my$a
#> [1] 1
my$b
#> [1] 2
my$f(my$a, my$b)
#> [1] 3

Use with() to aceess the objects with their bare names.

with(my, 
     f(a,b))
#> [1] 3

Attach a Module to the Search Path

Just like a package, a module can be attached to the search path.

use(my)

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.

f(a,b)
#> [1] 3

Detach the module from the search path when done, if desired.

drop("my")

Make objects Available to another Module

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 fs are available.

my_other <- module({
        refer(my, prefix = .)
        c <- 4
        d <- 5
        f <- function() print("foo") 
})

ls(my_other)
#> [1] "c"    "d"    "f"    "my.a" "my.b" "my.f"

my_other$my.f(1, 2)
#> [1] 3
my_other$f()
#> [1] "foo"

Public and Private Variables

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."

Use a package

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.

mpg_analysis <- module({
    require(ggplot2)
    plot <- qplot(mtcars$mpg)    
})
mpg_analysis$plot

Meanwhile, the global search path remain unaffected, not containing the ggplot2 package:

"package:ggplot2" %in% search()
#> [1] FALSE

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

Simulate OOP

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.

Notes

Environment

A module is an environment. This means that every rule that applies to environments, such as copy-by-reference, applies to modules as well.

mode(my)
#> [1] "environment"
is.environment(my)
#> [1] TRUE

Terms

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.

Locked

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.

my$a <- 1
#> Error in my$a <- 1: cannot change value of locked binding for 'a'
my$new_var <- 1
#> Error in my$new_var <- 1: cannot add bindings to a locked environment

Hidden Objects

As a general R rule, names that start with . define hidden objects.

my_yet_another <- module({
        .var <- "I'm hidden!"
})

Hidden objects are not returned by ls(), unless specified.

ls(my_yet_another)
#> character(0)
ls(my_yet_another, all.names = TRUE)
#> [1] ".var"

Nonetheless, in a module, they are treated the same as public objects.

my_yet_another$.var
#> [1] "I'm hidden!"

Load/Attach from File

module_path <- system.file("misc/example_module.R", package = "mod")

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:

use(module_path)
ls("module:example_module")
#> [1] "a" "d" "e"
a
#> [1] 1
d()
#> [1] 6
e(100)
#> [1] 106

Modules and Packages

As it could be confusing how to deal with modules and packages, the following clarification is made:

  • Attach a Package
    • To the local “search path”, at module context: require()
    • To the global search path, at global environment: require()
  • Attach a Module
    • To another module’s local “search path”: not available*
    • To the global search path: use()
  • Copy Objects from a Module
    • To another module: refer()
    • To the global environment: not available**
  • Use Modules inside a Package?
    • Yes, the package must 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.

Unattached

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:

detach("package:mod")

my_mind <- mod::ule({
  provide(good_thought)
  good_thought <- "I love working on this package!"
  bad_thought <- "I worry that no one will appreciate it."
})

mod::use(my_mind)
good_thought
#> [1] "I love working on this package!"