--- title: "Introduction to Modules" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{introduction} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Use The `mod` package is designed to be used either attached or unattached to your search path. The following demonstrations show the package attached: ```{r} require(mod) ``` ## Examples Define an inline module: ```{r} my <- module({ a <- 1 b <- 2 f <- function(x, y) x + y }) ``` The resulting module contains the objects defined within. ```{r} ls(my) ``` Subset the module to access its objects. ```{r} my$a my$b my$f(my$a, my$b) ``` Use `with()` to aceess the objects with their bare names. ```{r} with(my, f(a,b)) ``` ### Attach a Module to the Search Path Just like a package, a module can be attached to the search path. ```{r} use(my) ``` The `my` module is attached to the search path as "module:my". ```{r} search() ``` And you can use the objects inside directly, just like those from a package. ```{r} f(a,b) ``` Detach the module from the search path when done, if desired. ```{r} 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. ```{r} ls(my) my_other<- module({ refer(my) c <- 4 d <- 5 f <- function() print("foo") }) ls(my_other) my_other$f() ``` 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. ```{r} my_other <- module({ refer(my, prefix = .) c <- 4 d <- 5 f <- function() print("foo") }) ls(my_other) my_other$my.f(1, 2) my_other$f() ``` ### Public and Private Variables A variable is _private_ if its name starts with `..`. ```{r} 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. ```{r} ls(room_101) room_101$..diary room_101$get_diary() ``` If using `provide()` function to explicitly declair public variables, all others become private. ```{r} 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) room_102$open_info room_102$classified_info room_102$get_classified() ``` ### 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. ```{r eval = FALSE} mpg_analysis <- module({ require(ggplot2) plot <- qplot(mtcars$mpg) }) mpg_analysis$plot ``` Meanwhile, the global search path remain unaffected, not containing the `ggplot2` package: ```{r} "package:ggplot2" %in% search() ``` `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: ```{r} 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()) } }) ``` ```{r} bobby$cry() dad$complain() dad$call_mom() ``` 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`](https://CRAN.R-project.org/package=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. ```{r} mode(my) is.environment(my) ``` #### 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`](https://CRAN.R-project.org/package=reticulate) package also uses `import()`. To avoid confusion, we decided to introduce some synonyms. With analogous semantics, [`refer()`](https://clojuredocs.org/clojure.core/refer) is borrowed from Clojure, while [`provide()`](https://docs.racket-lang.org/reference/require.html?q=provide#%28form._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._provide%29%29) 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. ```{r error = TRUE} my$a <- 1 my$new_var <- 1 ``` #### Hidden Objects As a general R rule, names that start with `.` define hidden objects. ```{r} my_yet_another <- module({ .var <- "I'm hidden!" }) ``` Hidden objects are not returned by `ls()`, unless specified. ```{r} ls(my_yet_another) ls(my_yet_another, all.names = TRUE) ``` Nonetheless, in a module, they are treated the same as public objects. ```{r} my_yet_another$.var ``` #### Load/Attach from File ```{r} module_path <- system.file("misc/example_module.R", package = "mod") ``` To load and assign to object: ```{r} example_module <- acquire(module_path) ls(example_module) example_module$a example_module$d() example_module$e(100) ``` To load and attach to search path: ```{r} use(module_path) ls("module:example_module") a d() e(100) ``` #### 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: ```{r} 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 ```