messages.R

              
            

A dictionary of messages used by the package. We separate these into its own file to avoid cluttering the R code with a multitude of strings.

              messages <- list(
  invalid_module_path = c("Invalid path passed to the ",
    sQuote("path"), " parameter to the ", sQuote("module"),
    " function: I received a ", crayon::red("{{{klass}}}"), 
    " but was expecting a ", crayon::yellow("character"), "."),

  invalid_module_character_path = c("Invalid path passed to the ",
    sQuote("path"), " parameter to the ", sQuote("module"),
    " function: I expected a non-NA non-blank character vector
    of length 1")
)

            

Cleanse the message a little after fetching it from the messages list.

              msg <- function(name) {
  stopifnot(name %in% names(messages))

            

The gsub will squish multiple spaces into a single space, while the paste(collapse = "", ...) usage will ensure we can take vectors of characters in the above messages list.

                paste(collapse = "", gsub("[ ]+", " ", messages[[name]]))
}

            

We use the whisker templating engine to inject any additional values into the message string. For example,

m("invalid_module_path", klass = "bloop")

would return the appropriate error with the string “bloop” injected in the appropriate place.

              m <- function(name, ...) {
            

Note the use of do.call, a very handy R metaprogramming tool when we do not know exactly which arguments we will pass.

                do.call(whisker::whisker.render, list(msg(name), list(...)))
}


            

module.R

              
            
              # TODO: (RK) Dcoument modules more extensively.
            

Typical modularization of R code is done through packages. However, packages have a few design limitations that prevents their widespread use by developers.

  1. Packages do not solve the problem of hierarchical namespacing. In particular, languages like Ruby have good support for a primitive called a module and C++ has a primitive termed a namespace; R, despite being a functional language with roots to the LISP currently does not have a good hierachical namespacing system.
  2. Hierarchical file structures and exports. In particular, R packages are limited to flat directory structures for historical reasons decided by the R core team.

Both of these limitations are legitimate problems because organizing large codebases managed by teams of dozens or hundreds of developers is difficult or unpleasant without a sane hierarchical namespacing system.

Modules aim to partially allay this problem by building a hierarchical namespacing mechanism on top of environments, a well-defined and well-understood primitive data structure in R.

              #' Load and attach an R module in the current environment.
#'
#' Modules are light wrappers around environments that provide
#' better support for hierarching namespacing.
#'
#' @param path character. If \code{path} is a character vector giving
#'   an installed package in the current library path, a module
#'   encompassing that package will be returned. Otherwise, the
#'   directory given by the path will be loaded as a module.
#' @return a \code{module} object.
module <- function(path) {
  if (!is.character(path)) {
    stop(m("invalid_module_path", klass = class(path)[1L]))
  }
  
  if (!is.simple_string(path)) {
    stop(m("invalid_module_character_path"))
  }

  module_(path)
}

module_ <- function(path) {
  if (package_exists(path)) {
    object <- package(path)
  } else {
    object <- file(path)
  }

  module_new(object)
}

module_new <- function(object) {
  UseMethod("module_new")
}

module_new.file <- function(object) {
  NULL
}

module_new.package <- function(object) {
  NULL
}

            

package.module.R

              
            
              #' An R namespace system that supersedes packages by allowing nesting.
#'
#' @name module
#' @import module
#' @docType package
NULL
            

package.R

              
            
              #' An S3 class representing metadata about a package.
#'
#' @param name character. The name of the package. It
#'   must be installed in the user's currently active
#'   library.
package <- function(name) {
  # TODO: (RK) Find path using system.file, determine
  # if installed.
  structure(list(name = name), class = "package")
}

            

utils.R

              
            
              `%||%` <- function(x, y) if (is.null(x)) y else x

is.simple_string <- function(x) {
  is.character(x) && length(x) == 1 &&
  !is.na(x) && nzchar(x)
}

file <- function(path) {
}