From b279fc68cf23c335be75fa737e03a761381473d8 Mon Sep 17 00:00:00 2001 From: Konrad Rudolph Date: Fri, 17 Aug 2018 21:32:31 +0100 Subject: [PATCH] Deployment --- NAMESPACE | 19 + inst/doc/basic-usage.R | 66 + inst/doc/basic-usage.html | 1685 ++++++++++++++++++++++ inst/doc/basic-usage.md | 289 ++++ inst/doc/basic-usage.rmd | 227 +++ inst/doc/foreign-function-interface.R | 22 + inst/doc/foreign-function-interface.html | 1602 ++++++++++++++++++++ inst/doc/foreign-function-interface.md | 86 ++ inst/doc/foreign-function-interface.rmd | 79 + inst/doc/info.r | 2 + inst/doc/rcpp/__init__.r | 1 + inst/doc/rcpp/__install__.r | 59 + inst/doc/rcpp/compiled.r | 8 + inst/doc/rcpp/convolve.cpp | 14 + inst/doc/source-file.r | 15 + inst/doc/utils/__init__.r | 1 + inst/doc/utils/seq.r | 65 + man/attach_module.Rd | 28 + man/do_import.Rd | 18 + man/exhibit_namespace.Rd | 40 + man/export_operators.Rd | 30 + man/export_submodule.Rd | 37 + man/find_module.Rd | 20 + man/fix_module_attributes.Rd | 21 + man/help.Rd | 41 + man/import.Rd | 116 ++ man/import_search_path.Rd | 19 + man/is_S3_user_generic.Rd | 16 + man/loaded_modules.Rd | 50 + man/lsf.Rd | 14 + man/make_S3_methods_known.Rd | 14 + man/module_attributes.Rd | 31 + man/module_base_path.Rd | 18 + man/module_file.Rd | 35 + man/module_init_files.Rd | 27 + man/module_name.Rd | 38 + man/module_path.Rd | 17 + man/modules.Rd | 60 + man/register_S3_method.Rd | 45 + man/reload.Rd | 29 + man/script_path.Rd | 11 + man/set_script_path.Rd | 23 + man/split_path.Rd | 36 + man/unload.Rd | 31 + 44 files changed, 5105 insertions(+) create mode 100644 NAMESPACE create mode 100644 inst/doc/basic-usage.R create mode 100644 inst/doc/basic-usage.html create mode 100644 inst/doc/basic-usage.md create mode 100644 inst/doc/basic-usage.rmd create mode 100644 inst/doc/foreign-function-interface.R create mode 100644 inst/doc/foreign-function-interface.html create mode 100644 inst/doc/foreign-function-interface.md create mode 100644 inst/doc/foreign-function-interface.rmd create mode 100644 inst/doc/info.r create mode 100644 inst/doc/rcpp/__init__.r create mode 100644 inst/doc/rcpp/__install__.r create mode 100644 inst/doc/rcpp/compiled.r create mode 100644 inst/doc/rcpp/convolve.cpp create mode 100644 inst/doc/source-file.r create mode 100644 inst/doc/utils/__init__.r create mode 100644 inst/doc/utils/seq.r create mode 100644 man/attach_module.Rd create mode 100644 man/do_import.Rd create mode 100644 man/exhibit_namespace.Rd create mode 100644 man/export_operators.Rd create mode 100644 man/export_submodule.Rd create mode 100644 man/find_module.Rd create mode 100644 man/fix_module_attributes.Rd create mode 100644 man/help.Rd create mode 100644 man/import.Rd create mode 100644 man/import_search_path.Rd create mode 100644 man/is_S3_user_generic.Rd create mode 100644 man/loaded_modules.Rd create mode 100644 man/lsf.Rd create mode 100644 man/make_S3_methods_known.Rd create mode 100644 man/module_attributes.Rd create mode 100644 man/module_base_path.Rd create mode 100644 man/module_file.Rd create mode 100644 man/module_init_files.Rd create mode 100644 man/module_name.Rd create mode 100644 man/module_path.Rd create mode 100644 man/modules.Rd create mode 100644 man/register_S3_method.Rd create mode 100644 man/reload.Rd create mode 100644 man/script_path.Rd create mode 100644 man/set_script_path.Rd create mode 100644 man/split_path.Rd create mode 100644 man/unload.Rd diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 00000000..f9017b79 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,19 @@ +# Generated by roxygen2: do not edit by hand + +S3method("$",module) +S3method(print,module) +export("?") +export(help) +export(import) +export(import_) +export(import_package) +export(import_package_) +export(module_file) +export(module_help) +export(module_name) +export(register_S3_method) +export(reload) +export(set_script_path) +export(unload) +importFrom(stats,setNames) +importFrom(utils,lsf.str) diff --git a/inst/doc/basic-usage.R b/inst/doc/basic-usage.R new file mode 100644 index 00000000..1b096698 --- /dev/null +++ b/inst/doc/basic-usage.R @@ -0,0 +1,66 @@ +## ----include=FALSE------------------------------------------------------- +devtools::load_all() +import('source-file') + +## ------------------------------------------------------------------------ +seq = import('utils/seq') +ls() + +## ------------------------------------------------------------------------ +ls(seq) + +## ----eval=FALSE---------------------------------------------------------- +# ?seq$seq + +## ------------------------------------------------------------------------ +s = seq$seq(c(foo = 'GATTACAGATCAGCTCAGCACCTAGCACTATCAGCAAC', + bar = 'CATAGCAACTGACATCACAGCG')) +s + +## ------------------------------------------------------------------------ +seq$print.seq + +## ------------------------------------------------------------------------ +# We can unload loaded modules that we assigned to an identifier: +unload(seq) + +options(import.path = 'utils') +import('seq', attach = TRUE) + +## ------------------------------------------------------------------------ +search() + +## ------------------------------------------------------------------------ +detach('module:seq') # Name is optional +local({ + import('seq', attach = TRUE) + table('GATTACA') +}) + +## ------------------------------------------------------------------------ +search() +table('GATTACA') + +## ----file='utils/__init__.r'--------------------------------------------- + +## ------------------------------------------------------------------------ +options(import.path = NULL) # Reset search path +utils = import('utils') +ls(utils) +ls(utils$seq) +utils$seq$revcomp('CAT') + +## ----eval=FALSE---------------------------------------------------------- +# export_submodule('./seq') + +## ----file='info.r'------------------------------------------------------- + +## ------------------------------------------------------------------------ +info = import('info') + +## ------------------------------------------------------------------------ +import('info') + +## ------------------------------------------------------------------------ +reload(info) + diff --git a/inst/doc/basic-usage.html b/inst/doc/basic-usage.html new file mode 100644 index 00000000..1efa1965 --- /dev/null +++ b/inst/doc/basic-usage.html @@ -0,0 +1,1685 @@ + + + + + + + + + + + + + + + +Basic Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+

1 Basic module usage

+
+

1.1 The seq module

+

For the purpose of this tutorial, we are going to use the toy module utils/seq, which is implemented in the file utils/seq.r. The module implements some very basic mechanisms to deal with DNA sequences (character strings consisting entirely of the letters A, C, G and T).

+

First, we load the module.

+ +
## [1] "seq"
+

utils serves as a supermodule here, which groups several submodules (but for now, seq is the only one).

+

To see which functions a module exports, use ls:

+ +
## [1] "print.seq"         "revcomp"           "seq"              
+## [4] "table"             "valid_seq"         "valid_seq.default"
+## [7] "valid_seq.seq"
+

And we can display interactive help for individual functions:

+ +

This function creates a biological sequence. We can use it:

+ +
## >foo
+## GATTACAGATCAGCTCAGCACCTAGCACTATCAGCAAC
+## >bar
+## CATAGCAACTGACATCACAGCG
+

Notice how we get a pretty-printed, FASTA-like output because the print method is redefined for the seq class in utils/seq:

+ +
## function (seq, columns = 60) 
+## {
+##     lines = strsplit(seq, sprintf("(?<=.{%s})", columns), perl = TRUE)
+##     print_single = function(seq, name) {
+##         if (!is.null(name)) 
+##             cat(sprintf(">%s\n", name))
+##         cat(seq, sep = "\n")
+##     }
+##     names = if (is.null(names(seq))) 
+##         list(NULL)
+##     else names(seq)
+##     Map(print_single, lines, names)
+##     invisible(seq)
+## }
+## <environment: 0x7fcca2b58b08>
+
+
+

1.2 Attaching modules

+

That’s it for basic usage. In order to understand more about the module mechanism, let’s look at an alternative usage:

+ +

After unloading the already loaded module, the options function call sets the module search path: this is where import searches for modules. If more than one path is given, import searches them all until a module of matching name is found.

+

The import statement can now simply specify seq instead of utils/seq as the module name. We also specify attach=TRUE. This has an effect similar to package loading (or attaching an environment): all the module’s names are now available for direct use without necessitating the seq$ qualifier.

+

However, unlike the attach function, module attachment happens in local scope only. Since the above code was executed in global scope, there’s no distinction between local and global scope:

+ +
##  [1] ".GlobalEnv"        "module:seq"        "devtools_shims"   
+##  [4] "package:modules"   "package:stats"     "package:graphics" 
+##  [7] "package:grDevices" "package:utils"     "package:datasets" 
+## [10] "rprofile"          "Autoloads"         "package:base"
+

Notice the second position, which reads “module:seq”. But now let’s undo that, and attach (and use) the module locally instead.

+ +
## [[1]]
+## 
+## A C G T 
+## 3 1 1 2
+

Note that this uses seq’s table function, rather than base::table (which would have a different output). Furthermore, note that outside the local scope, the module is not attached:

+ +
##  [1] ".GlobalEnv"        "devtools_shims"    "package:modules"  
+##  [4] "package:stats"     "package:graphics"  "package:grDevices"
+##  [7] "package:utils"     "package:datasets"  "rprofile"         
+## [10] "Autoloads"         "package:base"
+ +
## 
+## GATTACA 
+##       1
+

This is very powerful, as it isolates separate scopes more effectively than the attach function. What is more, modules which are imported and attached inside another module remain inside that module and are not visible outside the module by default.

+

Nevertheless, the normal, recommended usage of a module is with attach=FALSE (the default), as this makes it clearer which names we are referring to.

+
+
+

1.3 Nested modules

+

Modules can also be nested in hierarchies. In fact, here is the implementation of utils (in utils/__init__.r: since utils is a directory rather than a file, the module implementation resides in the nested file __init__.r):

+ +

The submodule is specified as './seq' rather than 'seq': the explicitly provided relative path prevents lookup in the import search path (that we set via options(import.path=…) earlier); instead, only the current directory is considered.

+

We can now use the utils module:

+ +
## [1] "seq"
+ +
## [1] "print.seq"         "revcomp"           "seq"              
+## [4] "table"             "valid_seq"         "valid_seq.default"
+## [7] "valid_seq.seq"
+ +
## ATG
+

We could also have implemented utils as follows:

+ +

This would have made all of seq’s definitions immediately available in utils. This is sometimes useful, but should be employed with care.

+
+
+

1.4 Implementing modules

+

utils/seq.r is, by and large, a normal R source file. In fact, there are only two things worth mentioning:

+
    +
  1. Documentation. Each function in the module file is documented using the roxygen2 syntax. It works the same as for packages. The modules package parses the documentation and makes it available via module_help and ?.

  2. +
  3. The module exports S3 functions. The modules package takes care to register such functions automatically but this only works for user generics that are defined inside the same module. When overriding “known generics” (such as print), we need to register these manually via register_S3_method (this is necessary since these functions are inherently ambiguous and there is no automatic way of finding them).

  4. +
+

Module files can contain arbitrary code. It is executed when loaded for the first time: subsequent imports in the same session, regardless of whether they occur in a different scope, will refer to the loaded, cached module, and will not reload a module.

+

We can illustrate this by loading a module which has side-effects, 'info'.

+ +

Let’s load it:

+ +
## Loading module "info"
+
## Module path: "vignettes"
+

We have imported the module, and get the diagnostic messages. Let’s re-import the module:

+ +

… no messages are displayed. However, we can explicitly reload a module. This clears the cache, and loads the module again:

+ +
## Loading module "info"
+
## Module path: "vignettes"
+

And this displays the messages again. The reload function is a shortcut for unload followed by import (using the exact same arguments as used on the original import call).

+

The info module also show-cases two important helper functions:

+
    +
  1. module_name contains the name of the module with which it was loaded. This is especially handy because outside of a module module_name is NULL. We can harness this in a similar way to Python’s __name__ mechanism.

  2. +
  3. module_file works equivalently to system.file: it returns the full path to any file within a module. This is helpful when distributing data files with modules, which are loaded from within the module. When invoked without arguments, module_file returns the full path to the directory containing the module source file.

  4. +
+
+
+ + + +
+
+ +
+ + + + + + + + diff --git a/inst/doc/basic-usage.md b/inst/doc/basic-usage.md new file mode 100644 index 00000000..aa27643a --- /dev/null +++ b/inst/doc/basic-usage.md @@ -0,0 +1,289 @@ +Basic module usage +================== + +The `seq` module +---------------- + +For the purpose of this tutorial, we are going to use the toy module +`utils/seq`, which is implemented in the file +[`utils/seq.r`](utils/seq.r). The module implements some very basic +mechanisms to deal with DNA sequences (character strings consisting +entirely of the letters `A`, `C`, `G` and `T`). + +First, we load the module. + +``` r +seq = import('utils/seq') +ls() +``` + + ## [1] "seq" + +`utils` serves as a supermodule here, which groups several submodules +(but for now, `seq` is the only one). + +To see which functions a module exports, use `ls`: + +``` r +ls(seq) +``` + + ## [1] "print.seq" "revcomp" "seq" + ## [4] "table" "valid_seq" "valid_seq.default" + ## [7] "valid_seq.seq" + +And we can display interactive help for individual functions: + +``` r +?seq$seq +``` + +This function creates a biological sequence. We can use it: + +``` r +s = seq$seq(c(foo = 'GATTACAGATCAGCTCAGCACCTAGCACTATCAGCAAC', + bar = 'CATAGCAACTGACATCACAGCG')) +s +``` + + ## >foo + ## GATTACAGATCAGCTCAGCACCTAGCACTATCAGCAAC + ## >bar + ## CATAGCAACTGACATCACAGCG + +Notice how we get a pretty-printed, +[FASTA](http://en.wikipedia.org/wiki/FASTA_format)-like output because +the `print` method is redefined for the `seq` class in `utils/seq`: + +``` r +seq$print.seq +``` + + ## function (seq, columns = 60) + ## { + ## lines = strsplit(seq, sprintf("(?<=.{%s})", columns), perl = TRUE) + ## print_single = function(seq, name) { + ## if (!is.null(name)) + ## cat(sprintf(">%s\n", name)) + ## cat(seq, sep = "\n") + ## } + ## names = if (is.null(names(seq))) + ## list(NULL) + ## else names(seq) + ## Map(print_single, lines, names) + ## invisible(seq) + ## } + ## + +Attaching modules +----------------- + +That’s it for basic usage. In order to understand more about the module +mechanism, let’s look at an alternative usage: + +``` r +# We can unload loaded modules that we assigned to an identifier: +unload(seq) + +options(import.path = 'utils') +import('seq', attach = TRUE) +``` + +After unloading the already loaded module, the `options` function call +sets the module search path: this is where `import` searches for +modules. If more than one path is given, `import` searches them all +until a module of matching name is found. + +The `import` statement can now simply specify `seq` instead of +`utils/seq` as the module name. We also specify `attach=TRUE`. This has +an effect similar to package loading (or `attach`ing an environment): +all the module’s names are now available for direct use without +necessitating the `seq$` qualifier. + +However, unlike the `attach` function, module attachment happens *in +local scope* only. Since the above code was executed in global scope, +there’s no distinction between local and global scope: + +``` r +search() +``` + + ## [1] ".GlobalEnv" "module:seq" "devtools_shims" + ## [4] "package:modules" "package:stats" "package:graphics" + ## [7] "package:grDevices" "package:utils" "package:datasets" + ## [10] "rprofile" "Autoloads" "package:base" + +Notice the second position, which reads “module:seq”. But now let’s undo +that, and attach (and use) the module locally instead. + +``` r +detach('module:seq') # Name is optional +local({ + import('seq', attach = TRUE) + table('GATTACA') +}) +``` + + ## [[1]] + ## + ## A C G T + ## 3 1 1 2 + +Note that this uses `seq`’s `table` function, rather than `base::table` +(which would have a different output). Furthermore, note that *outside* +the local scope, the module is not attached: + +``` r +search() +``` + + ## [1] ".GlobalEnv" "devtools_shims" "package:modules" + ## [4] "package:stats" "package:graphics" "package:grDevices" + ## [7] "package:utils" "package:datasets" "rprofile" + ## [10] "Autoloads" "package:base" + +``` r +table('GATTACA') +``` + + ## + ## GATTACA + ## 1 + +This is very powerful, as it isolates separate scopes more effectively +than the `attach` function. What is more, modules which are imported and +attached inside another module *remain* inside that module and are not +visible outside the module by default. + +Nevertheless, the normal, recommended usage of a module is with +`attach=FALSE` (the default), as this makes it clearer which names we +are referring to. + +Nested modules +-------------- + +Modules can also be nested in hierarchies. In fact, here is the +implementation of `utils` (in [`utils/__init__.r`](utils/__init__.r): +since `utils` is a directory rather than a file, the module +implementation resides in the nested file `__init__.r`): + +``` r +seq = import('./seq') +``` + +The submodule is specified as `'./seq'` rather than `'seq'`: the +explicitly provided relative path prevents lookup in the import search +path (that we set via `options(import.path=…)` earlier); instead, only +the current directory is considered. + +We can now use the `utils` module: + +``` r +options(import.path = NULL) # Reset search path +utils = import('utils') +ls(utils) +``` + + ## [1] "seq" + +``` r +ls(utils$seq) +``` + + ## [1] "print.seq" "revcomp" "seq" + ## [4] "table" "valid_seq" "valid_seq.default" + ## [7] "valid_seq.seq" + +``` r +utils$seq$revcomp('CAT') +``` + + ## ATG + +We could also have implemented `utils` as follows: + +``` r +export_submodule('./seq') +``` + +This would have made all of `seq`’s definitions immediately available in +`utils`. This is sometimes useful, but should be employed with care. + +Implementing modules +-------------------- + +`utils/seq.r` is, by and large, a normal R source file. In fact, there +are only two things worth mentioning: + +1. Documentation. Each function in the module file is documented using + the + [roxygen2](http://cran.r-project.org/web/packages/roxygen2/index.html) + syntax. It works the same as for packages. The *modules* package + parses the documentation and makes it available via `module_help` + and `?`. + +2. The module exports [S3 functions](http://adv-r.had.co.nz/S3.html). + The *modules* package takes care to register such functions + automatically but this only works for *user generics* that are + defined inside the same module. When overriding “known generics” + (such as `print`), we need to register these manually via + `register_S3_method` (this is necessary since these functions are + inherently ambiguous and there is no automatic way of finding them). + +Module files can contain arbitrary code. It is executed when loaded for +the first time: subsequent `import`s in the same session, regardless of +whether they occur in a different scope, will refer to the loaded, +cached module, and will *not* reload a module. + +We can illustrate this by loading a module which has side-effects, +`'info'`. + +``` r +message('Loading module "', module_name(), '"') +message('Module path: "', basename(module_file()), '"') +``` + +Let’s load it: + +``` r +info = import('info') +``` + + ## Loading module "info" + + ## Module path: "vignettes" + +We have imported the module, and get the diagnostic messages. Let’s +re-import the module: + +``` r +import('info') +``` + +… no messages are displayed. However, we can explicitly *reload* a +module. This clears the cache, and loads the module again: + +``` r +reload(info) +``` + + ## Loading module "info" + + ## Module path: "vignettes" + +And this displays the messages again. The `reload` function is a +shortcut for `unload` followed by `import` (using the exact same +arguments as used on the original `import` call). + +The `info` module also show-cases two important helper functions: + +1. `module_name` contains the name of the module with which it was + loaded. This is especially handy because outside of a module + `module_name` is `NULL`. We can harness this in a similar way to + Python’s `__name__` mechanism. + +2. `module_file` works equivalently to `system.file`: it returns the + full path to any file within a module. This is helpful when + distributing data files with modules, which are loaded from within + the module. When invoked without arguments, `module_file` returns + the full path to the directory containing the module source file. diff --git a/inst/doc/basic-usage.rmd b/inst/doc/basic-usage.rmd new file mode 100644 index 00000000..152afb51 --- /dev/null +++ b/inst/doc/basic-usage.rmd @@ -0,0 +1,227 @@ +--- +title: "Basic Usage" +author: Konrad Rudolph +date: "`r Sys.Date()`" +output: + rmarkdown::html_document: + theme: "cerulean" + toc: true + toc_float: + collapsed: false + number_sections: true + highlight: "tango" + md_document: + variant: markdown_github +vignette: > + %\VignetteEngine{knitr::rmarkdown} + %\VignetteIndexEntry{Basic usage} + %\VignetteEncoding{UTF-8} +--- +```{r include=FALSE} +devtools::load_all() +import('source-file') +``` + +# Basic module usage + +## The `seq` module + +For the purpose of this tutorial, we are going to use the toy module +`utils/seq`, which is implemented in the file [`utils/seq.r`](utils/seq.r). +The module implements some very basic mechanisms to deal with DNA sequences +(character strings consisting entirely of the letters `A`, `C`, `G` and `T`). + +First, we load the module. + +```{r} +seq = import('utils/seq') +ls() +``` + +`utils` serves as a supermodule here, which groups several submodules (but for +now, `seq` is the only one). + +To see which functions a module exports, use `ls`: + +```{r} +ls(seq) +``` + +And we can display interactive help for individual functions: + +```{r eval=FALSE} +?seq$seq +``` + +This function creates a biological sequence. We can use it: + +```{r} +s = seq$seq(c(foo = 'GATTACAGATCAGCTCAGCACCTAGCACTATCAGCAAC', + bar = 'CATAGCAACTGACATCACAGCG')) +s +``` + +Notice how we get a pretty-printed, +[FASTA](http://en.wikipedia.org/wiki/FASTA_format)-like output because the +`print` method is redefined for the `seq` class in `utils/seq`: + +```{r} +seq$print.seq +``` + +## Attaching modules + +That’s it for basic usage. In order to understand more about the module +mechanism, let’s look at an alternative usage: + +```{r} +# We can unload loaded modules that we assigned to an identifier: +unload(seq) + +options(import.path = 'utils') +import('seq', attach = TRUE) +``` + +After unloading the already loaded module, the `options` function call sets +the module search path: this is where `import` searches for modules. If more +than one path is given, `import` searches them all until a module of matching +name is found. + +The `import` statement can now simply specify `seq` instead of `utils/seq` as +the module name. We also specify `attach=TRUE`. This has an effect similar to +package loading (or `attach`ing an environment): all the module’s names are +now available for direct use without necessitating the `seq$` qualifier. + +However, unlike the `attach` function, module attachment happens *in local +scope* only. Since the above code was executed in global scope, there’s no +distinction between local and global scope: + +```{r} +search() +``` + +Notice the second position, which reads “`r search()[2]`”. But now let’s undo +that, and attach (and use) the module locally instead. + +```{r} +detach('module:seq') # Name is optional +local({ + import('seq', attach = TRUE) + table('GATTACA') +}) +``` + +Note that this uses `seq`’s `table` function, rather than `base::table` (which +would have a different output). Furthermore, note that *outside* the local +scope, the module is not attached: + +```{r} +search() +table('GATTACA') +``` + +This is very powerful, as it isolates separate scopes more effectively than +the `attach` function. What is more, modules which are imported and attached +inside another module *remain* inside that module and are not visible outside +the module by default. + +Nevertheless, the normal, recommended usage of a module is with `attach=FALSE` +(the default), as this makes it clearer which names we are referring to. + +## Nested modules + +Modules can also be nested in hierarchies. In fact, here is the implementation +of `utils` (in [`utils/__init__.r`](utils/__init__.r): since `utils` is a +directory rather than a file, the module implementation resides in the nested +file `__init__.r`): + +```{r file='utils/__init__.r'} +``` + +The submodule is specified as `'./seq'` rather than `'seq'`: the +explicitly provided relative path prevents lookup in the import search path +(that we set via `options(import.path=…)` earlier); instead, only the current +directory is considered. + +We can now use the `utils` module: + +```{r} +options(import.path = NULL) # Reset search path +utils = import('utils') +ls(utils) +ls(utils$seq) +utils$seq$revcomp('CAT') +``` + +We could also have implemented `utils` as follows: + +```{r eval=FALSE} +export_submodule('./seq') +``` + +This would have made all of `seq`’s definitions immediately available in +`utils`. This is sometimes useful, but should be employed with care. + +## Implementing modules + +`utils/seq.r` is, by and large, a normal R source file. In fact, there are only +two things worth mentioning: + +1. Documentation. Each function in the module file is documented using the + [roxygen2](http://cran.r-project.org/web/packages/roxygen2/index.html) + syntax. It works the same as for packages. The *modules* package parses the + documentation and makes it available via `module_help` and `?`. + +2. The module exports [S3 functions](http://adv-r.had.co.nz/S3.html). The + *modules* package takes care to register such functions automatically but + this only works for *user generics* that are defined inside the same module. + When overriding “known generics” (such as `print`), we need to register + these manually via `register_S3_method` (this is necessary since these + functions are inherently ambiguous and there is no automatic way of finding + them). + +Module files can contain arbitrary code. It is executed when loaded for the +first time: subsequent `import`s in the same session, regardless of whether they +occur in a different scope, will refer to the loaded, cached module, and will +*not* reload a module. + +We can illustrate this by loading a module which has side-effects, `'info'`. + +```{r file='info.r'} +``` + +Let’s load it: + +```{r} +info = import('info') +``` + +We have imported the module, and get the diagnostic messages. Let’s re-import +the module: + +```{r} +import('info') +``` + +… no messages are displayed. However, we can explicitly *reload* a module. This +clears the cache, and loads the module again: + +```{r} +reload(info) +``` + +And this displays the messages again. The `reload` function is a shortcut for +`unload` followed by `import` (using the exact same arguments as used on the +original `import` call). + +The `info` module also show-cases two important helper functions: + +1. `module_name` contains the name of the module with which it was loaded. This + is especially handy because outside of a module `module_name` is `NULL`. We + can harness this in a similar way to Python’s `__name__` mechanism. + +2. `module_file` works equivalently to `system.file`: it returns the full path + to any file within a module. This is helpful when distributing data files + with modules, which are loaded from within the module. When invoked without + arguments, `module_file` returns the full path to the directory containing + the module source file. diff --git a/inst/doc/foreign-function-interface.R b/inst/doc/foreign-function-interface.R new file mode 100644 index 00000000..c2ba8c19 --- /dev/null +++ b/inst/doc/foreign-function-interface.R @@ -0,0 +1,22 @@ +## ----include=FALSE------------------------------------------------------- +devtools::load_all() +import('source-file') + +## ----file='rcpp/__init__.r'---------------------------------------------- + +## ----file='rcpp/convolve.cpp'-------------------------------------------- + +## ------------------------------------------------------------------------ +rcpp = import('rcpp') +ls(rcpp) +rcpp$convolve(1 : 3, 1 : 5) + +## ----echo=FALSE---------------------------------------------------------- +import('rcpp/__install__') + +## ----file='rcpp/compiled.r'---------------------------------------------- + +## ------------------------------------------------------------------------ +compiled = import('rcpp/compiled') +compiled$convolve(1 : 3, 1 : 5) + diff --git a/inst/doc/foreign-function-interface.html b/inst/doc/foreign-function-interface.html new file mode 100644 index 00000000..d501ac89 --- /dev/null +++ b/inst/doc/foreign-function-interface.html @@ -0,0 +1,1602 @@ + + + + + + + + + + + + + + + +Foreign function interface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+

1 Foreign function interface

+

Modules don’t have a built-in foreign function interface yet but it is possible to integrate C++ code via the excellent Rcpp package.

+
+

1.1 Ad-hoc compilation

+

As an example, take a look at the rcpp module found under inst/doc; the module consists of a C++ source file which is loaded inside the __init__.r file:

+ +

Here’s the C++ code itself (the example is taken from the Rcpp documentation):

+ +

This module can be used like any normal module:

+ +
## [1] "convolve"
+ +
## [1]  1  4 10 16 22 22 15
+
+
+

1.2 Ahead-of-time compilation

+

Unfortunately, this has a rather glaring flaw: the code is recompiled for each new R session. In order to avoid this, we need to compile the code once and save the resulting dynamic library. There’s no straightforward way of doing this, but Rcpp wraps R CMD SHLIB.

+

For the time being, we manually need to trigger compilation by executing the __install__.r file found in the inst/doc/rcpp module path.

+

Once that’s done, the actual module code is easy enough:

+ +

We can use it like any other module:

+ +
## [1]  1  4 10 16 22 22 15
+
+
+ + + +
+
+ +
+ + + + + + + + diff --git a/inst/doc/foreign-function-interface.md b/inst/doc/foreign-function-interface.md new file mode 100644 index 00000000..9d88169d --- /dev/null +++ b/inst/doc/foreign-function-interface.md @@ -0,0 +1,86 @@ +Foreign function interface +========================== + +Modules don’t have a built-in foreign function interface yet but it is +possible to integrate C++ code via the excellent +[Rcpp](http://cran.r-project.org/web/packages/Rcpp/index.html) package. + +Ad-hoc compilation +------------------ + +As an example, take a look at the `rcpp` module found under `inst/doc`; +the module consists of a C++ source file which is loaded inside the +`__init__.r` file: + +``` r +Rcpp::sourceCpp(module_file('convolve.cpp'), env = environment()) +``` + +Here’s the C++ code itself (the example is taken from the Rcpp +documentation): + +``` cpp +#include "Rcpp.h" + +using Rcpp::NumericVector; + +// [[Rcpp::export]] +NumericVector convolve(NumericVector a, NumericVector b) { + int na = a.size(), nb = b.size(); + int nab = na + nb - 1; + NumericVector xab(nab); + for (int i = 0; i < na; i++) + for (int j = 0; j < nb; j++) + xab[i + j] += a[i] * b[j]; + return xab; +} +``` + +This module can be used like any normal module: + +``` r +rcpp = import('rcpp') +ls(rcpp) +``` + + ## [1] "convolve" + +``` r +rcpp$convolve(1 : 3, 1 : 5) +``` + + ## [1] 1 4 10 16 22 22 15 + +Ahead-of-time compilation +------------------------- + +Unfortunately, this has a rather glaring flaw: the code is recompiled +for each new R session. In order to avoid this, we need to compile the +code *once* and save the resulting dynamic library. There’s no +straightforward way of doing this, but Rcpp wraps `R CMD SHLIB`. + +For the time being, we manually need to trigger compilation by executing +the [`__install__.r`](rcpp/__install__.r) file found in the +`inst/doc/rcpp` module path. + +Once that’s done, the actual module code is easy enough: + +``` r +# Load compiled module meta information, and load R wrapper code, which, in +# turn, loads the compiled module via `dyn.load`. +load_dynamic = function (prefix) { + context = readRDS(module_file(sprintf('%s.rds', prefix))) + source(context$rSourceFilename, local = parent.frame()) +} + +load_dynamic('convolve') +``` + +We can use it like any other module: + +``` r +compiled = import('rcpp/compiled') +compiled$convolve(1 : 3, 1 : 5) +``` + + ## [1] 1 4 10 16 22 22 15 diff --git a/inst/doc/foreign-function-interface.rmd b/inst/doc/foreign-function-interface.rmd new file mode 100644 index 00000000..c0a95018 --- /dev/null +++ b/inst/doc/foreign-function-interface.rmd @@ -0,0 +1,79 @@ +--- +title: "Foreign function interface" +author: Konrad Rudolph +date: "`r Sys.Date()`" +output: + rmarkdown::html_document: + theme: "cerulean" + toc: true + toc_float: + collapsed: false + number_sections: true + highlight: "tango" + md_document: + variant: markdown_github +vignette: > + %\VignetteEngine{knitr::rmarkdown} + %\VignetteIndexEntry{Foreign function interface} + %\VignetteEncoding{UTF-8} +--- +```{r include=FALSE} +devtools::load_all() +import('source-file') +``` + +# Foreign function interface + +Modules don’t have a built-in foreign function interface yet but it is possible +to integrate C++ code via the excellent [Rcpp][] package. + +## Ad-hoc compilation + +As an example, take a look at the `rcpp` module found under `inst/doc`; the +module consists of a C++ source file which is loaded inside the `__init__.r` +file: + +```{r file='rcpp/__init__.r'} +``` + +Here’s the C++ code itself (the example is taken from the Rcpp documentation): + +```{r file='rcpp/convolve.cpp'} +``` + +This module can be used like any normal module: + +```{r} +rcpp = import('rcpp') +ls(rcpp) +rcpp$convolve(1 : 3, 1 : 5) +``` + +## Ahead-of-time compilation + +Unfortunately, this has a rather glaring flaw: the code is recompiled for each +new R session. In order to avoid this, we need to compile the code *once* and +save the resulting dynamic library. There’s no straightforward way of doing +this, but Rcpp wraps `R CMD SHLIB`. + +For the time being, we manually need to trigger compilation by executing the +[`__install__.r`][install.r] file found in the `inst/doc/rcpp` module path. + +```{r echo=FALSE} +import('rcpp/__install__') +``` + +Once that’s done, the actual module code is easy enough: + +```{r file='rcpp/compiled.r'} +``` + +We can use it like any other module: + +```{r} +compiled = import('rcpp/compiled') +compiled$convolve(1 : 3, 1 : 5) +``` + +[Rcpp]: http://cran.r-project.org/web/packages/Rcpp/index.html +[install.r]: rcpp/__install__.r diff --git a/inst/doc/info.r b/inst/doc/info.r new file mode 100644 index 00000000..c892c231 --- /dev/null +++ b/inst/doc/info.r @@ -0,0 +1,2 @@ +message('Loading module "', module_name(), '"') +message('Module path: "', basename(module_file()), '"') diff --git a/inst/doc/rcpp/__init__.r b/inst/doc/rcpp/__init__.r new file mode 100644 index 00000000..09f12b50 --- /dev/null +++ b/inst/doc/rcpp/__init__.r @@ -0,0 +1 @@ +Rcpp::sourceCpp(module_file('convolve.cpp'), env = environment()) diff --git a/inst/doc/rcpp/__install__.r b/inst/doc/rcpp/__install__.r new file mode 100644 index 00000000..8db88d82 --- /dev/null +++ b/inst/doc/rcpp/__install__.r @@ -0,0 +1,59 @@ +# Helper functions. + +rootname = function (file, ext = '') + paste0(sub('\\.[^.]*$', '', file), '.', ext) + +rxescape = function (str) + gsub('([.?*+^$()\\{\\}|-]|\\[|\\])', '\\\\\\1', str) + +# C++ source; could potentially be more than one file. + +file = modules::module_file('convolve.cpp') + +# The following uses Rcpp to compile (and later, load) the code. This isn’t +# necessary, but it helps quite a bit. Ideally this should be generalisable +# though: while a dependency on Rcpp is fine, it’s not acceptable to limit +# external language support to C++. + +cache_dir = getOption("rcpp.cache.dir", tempdir()) +cache_dir = path.expand(cache_dir) +cache_dir = Rcpp:::.sourceCppPlatformCacheDir(cache_dir) +cache_dir = normalizePath(cache_dir) + +context = .Call('sourceCppContext', PACKAGE = 'Rcpp', file, NULL, TRUE, + cache_dir, .Platform) + +# Compile the code +local({ + cmd = sprintf('%s/R CMD SHLIB -o %s %s', + R.home('bin'), + shQuote(context$dynlibFilename), + shQuote(context$cppSourceFilename)) + + curdir = getwd() + env = as.list(vapply(c('PKG_CPPFLAGS', 'PKG_LIBS'), Sys.getenv, '')) + + on.exit({ + setwd(curdir) + Rcpp:::.restoreEnvironment(env) + }) + + setwd(context$buildDirectory) + Sys.setenv(PKG_CPPFLAGS = Rcpp:::RcppCxxFlags()) + Sys.setenv(PKG_LIBS = Rcpp:::RcppLdFlags()) + system(cmd) +}) + +patch_r_binding = function () { + source_file = file.path(context$buildDirectory, context$rSourceFilename) + source = readLines(source_file) + source = gsub(rxescape(context$dynlibPath), context$dynlibFilename, source) + writeLines(source, context$rSourceFilename) +} + +# Copy compiled sources and R wrapper code to module directory. +context$dynlibFilename = normalizePath(rootname(file, 'so'), mustWork = FALSE) +file.copy(context$dynlibPath, context$dynlibFilename) +patch_r_binding() +# Make compiled module meta information available to module. +saveRDS(context, file = rootname(file, 'rds')) diff --git a/inst/doc/rcpp/compiled.r b/inst/doc/rcpp/compiled.r new file mode 100644 index 00000000..295d3161 --- /dev/null +++ b/inst/doc/rcpp/compiled.r @@ -0,0 +1,8 @@ +# Load compiled module meta information, and load R wrapper code, which, in +# turn, loads the compiled module via `dyn.load`. +load_dynamic = function (prefix) { + context = readRDS(module_file(sprintf('%s.rds', prefix))) + source(context$rSourceFilename, local = parent.frame()) +} + +load_dynamic('convolve') diff --git a/inst/doc/rcpp/convolve.cpp b/inst/doc/rcpp/convolve.cpp new file mode 100644 index 00000000..5d0b3d30 --- /dev/null +++ b/inst/doc/rcpp/convolve.cpp @@ -0,0 +1,14 @@ +#include "Rcpp.h" + +using Rcpp::NumericVector; + +// [[Rcpp::export]] +NumericVector convolve(NumericVector a, NumericVector b) { + int na = a.size(), nb = b.size(); + int nab = na + nb - 1; + NumericVector xab(nab); + for (int i = 0; i < na; i++) + for (int j = 0; j < nb; j++) + xab[i + j] += a[i] * b[j]; + return xab; +} diff --git a/inst/doc/source-file.r b/inst/doc/source-file.r new file mode 100644 index 00000000..09a4e66a --- /dev/null +++ b/inst/doc/source-file.r @@ -0,0 +1,15 @@ +source_file = function (path, language) { + if (missing(language) || is.null(language)) + language = regmatches(path, regexpr('[^.]*$', path)) + code = paste(readLines(path), collapse = '\n') + sprintf('```%s\n%s\n```', language, code) +} + +knitr::knit_hooks$set(file = function (before, options, envir) { + if (! before) { + # Necessary because this file is built both as vignette and + # independent, and this happens from different working directories. + path_prefix = if (grepl('vignettes$', getwd())) '.' else 'vignettes' + source_file(file.path(path_prefix, options$file), options$lang) + } +}) diff --git a/inst/doc/utils/__init__.r b/inst/doc/utils/__init__.r new file mode 100644 index 00000000..83fd728d --- /dev/null +++ b/inst/doc/utils/__init__.r @@ -0,0 +1 @@ +seq = import('./seq') diff --git a/inst/doc/utils/seq.r b/inst/doc/utils/seq.r new file mode 100644 index 00000000..e925ae12 --- /dev/null +++ b/inst/doc/utils/seq.r @@ -0,0 +1,65 @@ +#' Test whether input is valid biological sequence +#' @param seq a character vector or \code{seq} object +valid_seq = function (seq) + UseMethod('valid_seq') + +valid_seq.default = function (seq) { + valid = function (x) + ! any(is.na(match(strsplit(x, '')[[1]], c('A', 'C', 'G', 'T')))) + all(vapply(toupper(seq), valid, logical(1))) +} + +valid_seq.seq = function (seq) + TRUE + +#' Create a biological sequence +#' +#' Create a nucleotide sequence consisting of \code{A}, \code{C}, \code{G} and +#' \code{T} +#' @param x character vector +#' @param validate logical to indicate whether to validate the input +#' (default: \code{TRUE}), via \code{\link{valid_seq}} +#' @return Biological sequence equivalent to the input string +seq = function (x, validate = TRUE) { + if (validate) + stopifnot(valid_seq(x)) + structure(toupper(x), class = 'seq') +} + +#' Print one or more biological sequences +#' @param seq biological sequences +print.seq = function (seq, columns = 60) { + lines = strsplit(seq, sprintf('(?<=.{%s})', columns), perl = TRUE) + print_single = function (seq, name) { + if (! is.null(name)) + cat(sprintf('>%s\n', name)) + cat(seq, sep = '\n') + } + names = if (is.null(names(seq))) list(NULL) else names(seq) + Map(print_single, lines, names) + invisible(seq) +} + +register_S3_method('print', 'seq', print.seq) + +#' Reverse complement +#' +#' The reverse complement of a sequence is its reverse, with all bases +#' substituted by their base complement. +#' @param seq character vector of biological sequences +revcomp = function (seq) { + rc = function (seq) { + bases = strsplit(chartr('ACGT', 'TGCA', seq), '')[[1]] + paste(rev(bases), collapse = '') + } + `class<-`(setNames(vapply(seq, rc, character(1)), names(seq)), class = 'seq') +} + +#' Tabulate nucleotides present in sequence +#' @param seq sequences +#' @return A \code{\link[base::table]{table}} for the nucleotides of each +#' sequence in the input. +table = function (seq) + setNames(lapply(lapply(strsplit(seq, ''), factor, c('A', 'C', 'G', 'T')), + base::table), + names(seq)) diff --git a/man/attach_module.Rd b/man/attach_module.Rd new file mode 100644 index 00000000..7f4fd1c1 --- /dev/null +++ b/man/attach_module.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{attach_module} +\alias{attach_module} +\title{Attach a module environment locally or globally} +\usage{ +attach_module(all, operators, name, mod_env, parent) +} +\arguments{ +\item{all}{logical specifying whether to attach the whole module} + +\item{operators}{logical specifying whether to attach operators} + +\item{name}{the module name} + +\item{mod_env}{the module environment to attach} + +\item{parent}{the module parent environment} +} +\description{ +Attach a module environment locally or globally +} +\details{ +If neither \code{all} nor \code{operators} are \code{TRUE}, this function +does nothing. Otherwise, it will either attach the whole module environment +or the operators it exports. Attaching is done by inserting the module at the +first position into the parent environment chain. +} diff --git a/man/do_import.Rd b/man/do_import.Rd new file mode 100644 index 00000000..497782f2 --- /dev/null +++ b/man/do_import.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{do_import} +\alias{do_import} +\title{Perform the actual import operation on an individual module} +\usage{ +do_import(module_name, module_path, doc) +} +\arguments{ +\item{module_name}{the name of the module, as specified by the user} + +\item{module_path}{the fully resolved path to the module file} + +\item{doc}{logical, whether the module documentation should be parsed} +} +\description{ +Perform the actual import operation on an individual module +} diff --git a/man/exhibit_namespace.Rd b/man/exhibit_namespace.Rd new file mode 100644 index 00000000..11c543a5 --- /dev/null +++ b/man/exhibit_namespace.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/exhibit_namespace.r +\name{exhibit_namespace} +\alias{exhibit_namespace} +\alias{exhibit_module_namespace} +\alias{exhibit_package_namespace} +\title{Exhibit objects from an internal module namespace as an environment} +\usage{ +exhibit_namespace(objects, name, path, doc, parent) + +exhibit_module_namespace(namespace, name, parent, export_list) + +exhibit_package_namespace(namespace, name, parent, export_list) +} +\arguments{ +\item{objects}{named list of objects} + +\item{name}{the name of the resulting environment} + +\item{path}{the fully resolved path to the corresponding module or package} + +\item{doc}{the module documentation} + +\item{parent}{the parent environment of the resulting environment} + +\item{namespace}{the namespace to export object from} + +\item{export_list}{the list of objects to export; if \code{NULL}, export +everything} +} +\value{ +Returns the resulting environment. +} +\description{ +Exhibit objects from an internal module namespace as an environment + +\code{exhibit_module_namespace} exports a namespace for a module. + +\code{exhibit_package_namespace} exports a namespace for a package. +} diff --git a/man/export_operators.Rd b/man/export_operators.Rd new file mode 100644 index 00000000..7471c88d --- /dev/null +++ b/man/export_operators.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{export_operators} +\alias{export_operators} +\title{Copy a module’s operators into a separate environment} +\usage{ +export_operators(environment, parent, module_name) +} +\arguments{ +\item{environment}{the module environment} + +\item{parent}{the parent environment of the calling code, to determine how to +chain the environments properly} + +\item{module_name}{the name of the module} +} +\value{ +A new environment containing the operators of the module. +} +\description{ +This function is used to create an attachable environment containing only the +module’s operators, as these would otherwise not be readily usable. +} +\note{ +This function expects the module \emph{environment} rather than the +\emph{namespace}. This is important because, for package modules, we only +want to expose exported operators, and not all exported operators are visible +inside the namespace (see, for example, \code{\link[dplyr]{\%>\%}}, which is +imported from the package “magrittr”). +} diff --git a/man/export_submodule.Rd b/man/export_submodule.Rd new file mode 100644 index 00000000..5a09f3d9 --- /dev/null +++ b/man/export_submodule.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/export_submodule.r +\name{export_submodule_} +\alias{export_submodule_} +\alias{export_submodule} +\title{Export a given submodule from the current module} +\usage{ +export_submodule_(submodule) + +export_submodule(submodule) +} +\arguments{ +\item{submodule}{character string of length 1 with the name of the submodule} +} +\description{ +Export a given submodule from the current module +} +\note{ +Sometimes, a module may want to export all or some of its submodules in +bulk. Simply doing \code{import('submodule', attach = TRUE)} won’t work, +however, since \code{attach} only has a local effect. \code{export_submodule}, +by contrast, exports a submodule’s contents as if they were defined directly +inside the current module. +} +\examples{ +\dontrun{ +# x/__init__.r: +export_submodule('./foo') + +# x/foo.r: +answer_to_life = function () 42 + +# Calling code can now use the above modules: +x = import('x') +x$answer_to_life() # returns 42 +} +} diff --git a/man/find_module.Rd b/man/find_module.Rd new file mode 100644 index 00000000..6b0c6a5a --- /dev/null +++ b/man/find_module.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/find_module.r +\name{find_module} +\alias{find_module} +\title{Find a module’s source code location} +\usage{ +find_module(module) +} +\arguments{ +\item{module}{character string of the fully qualified module name} +} +\value{ +the full path to the corresponding module source code location. If +multiple hits are found, return the one with the highest priority, that is +coming earlier in the search path, with the local directory having the +lowest priority. If no path is found, return \code{NA}. +} +\description{ +Find a module’s source code location +} diff --git a/man/fix_module_attributes.Rd b/man/fix_module_attributes.Rd new file mode 100644 index 00000000..fed31ead --- /dev/null +++ b/man/fix_module_attributes.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{fix_module_attributes} +\alias{fix_module_attributes} +\title{Save a module’s inherited attributes into a local environment} +\usage{ +fix_module_attributes(envir) +} +\arguments{ +\item{envir}{the environment into which to copy the inherited attributes} +} +\description{ +Save a module’s inherited attributes into a local environment +} +\details{ +Attaching a module to an environment makes the environment “forget” its +actual enclosing environment. This is important since that module holds +information such as the module name or path, which may be required in the +current local scope. To fix this, \code{fix_module_attributes} copies the +enclosing module’s attributes into the local environment. +} diff --git a/man/help.Rd b/man/help.Rd new file mode 100644 index 00000000..1247c9f2 --- /dev/null +++ b/man/help.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/help.r +\name{module_help} +\alias{module_help} +\alias{?} +\alias{help} +\title{Display module documentation} +\usage{ +module_help(topic, help_type = getOption("help_type", "text")) + +# ?module$function + +# help(module$function) +} +\arguments{ +\item{topic}{fully-qualified name of the object or function to get help for, +in the format \code{module$function}} + +\item{help_type}{character string specifying the output format; currently, +only \code{'text'} is supported} +} +\description{ +\code{module_help} displays help on a module’s objects and functions in much +the same way \code{\link[utils]{help}} does for package contents. +} +\note{ +Help is only available if \code{\link{import}} loaded the help stored +in the module file(s). By default, this happens only in interactive sessions. +} +\examples{ +\dontrun{ +mod = import('mod') +module_help(mod$func) +} +\dontrun{ +?mod$func +} +\dontrun{ +help(mod$func) +} +} diff --git a/man/import.Rd b/man/import.Rd new file mode 100644 index 00000000..84d4b823 --- /dev/null +++ b/man/import.Rd @@ -0,0 +1,116 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r, R/import_package.r +\name{import_} +\alias{import_} +\alias{import} +\alias{import_package_} +\alias{import_package} +\title{Import a module into the current scope} +\usage{ +import_(module, attach, attach_operators = TRUE, doc) + +import(module, attach, attach_operators = TRUE, doc) + +import_package_(package, attach, attach_operators = TRUE) + +import_package(package, attach, attach_operators = TRUE) +} +\arguments{ +\item{module}{a character string specifying the full module path} + +\item{attach}{either a boolean or a character vector. If \code{TRUE}, attach +the newly loaded module to the object search path (see \code{Details}). +Alternatively, if a character vector is given, attach only the listed names.} + +\item{attach_operators}{if \code{TRUE}, attach operators of module to the +object search path, even if \code{attach} is \code{FALSE}} + +\item{doc}{boolean specifying whether to load the module’s documentation (see +\code{Details})} + +\item{package}{a character string specifying the package name} +} +\value{ +the loaded module environment (invisible) +} +\description{ +\code{module = import('module')} imports a specified module and makes its +code available via the environment-like object it returns. +} +\details{ +Modules are loaded in an isolated environment which is returned, and +optionally attached to the object search path of the current scope (if +argument \code{attach} is \code{TRUE}). +\code{attach} defaults to \code{FALSE}. However, in interactive code it is +often helpful to attach packages by default. Therefore, in interactive code +invoked directly from the terminal only (i.e. not within modules), +\code{attach} defaults to the value of \code{options('import.attach')}, which +can be set to \code{TRUE} or \code{FALSE} depending on the user’s preference. + +\code{attach_operators} causes \emph{operators} to be attached by default, +because operators can only be invoked in R if they re found in the search +path. Not attaching them therefore drastically limits a module’s usefulness. + +\code{doc} loads the module’s documentation, specified as roxygen comments. +It defaults to \code{TRUE} in interactive mode and to \code{FALSE} otherwise. + +Modules are searched in the module search path \code{options('import.path')}. +This is a vector of paths to consider, from the highest to the lowest +priority. The current directory is \emph{always} considered last. That is, +if a file \code{a.r} exists both in the current directory and in a module +search path, the local file \code{./a.r} will not be loaded, unless the +import is explicitly specified as \code{import('./a')}. + +Module names can be fully qualified to refer to nested paths. See +\code{Examples}. + +Module source code files are assumed to be encoded in UTF-8 without BOM. +Ensure that this is the case when using an extended character set. + +\code{pkg = import_package('pkg')} imports a package and treats it much as if +it were a module, making package contents available in the \code{pkg} +variable. +} +\note{ +Unlike for \code{\link{library}}, attaching happens \emph{locally}: if +\code{import} is executed in the global environment, the effect is the same. +Otherwise, the imported module is inserted as the parent of the current +\code{environment()}. When used (globally) \emph{inside} a module, the newly +imported module is only available inside the module’s search path, not +outside it (nor in other modules which might be loaded). +} +\examples{ +\dontrun{ +# `a.r` is a file in the local directory containing a function `f`. +a = import('a') +a$f() + +# b/c.r is a file in path `b`, containing functions `f` and `g`. +import('b/c', attach = 'f') +# No module name qualification necessary +f() +g() # Error: could not find function "g" + +import('b/c', attach = TRUE) +f() +g() +} +\dontrun{ +dplyr = import_package('dplyr') +# Not attached, so we cannot do: +#cars = tbl_df(cars) +# Instead, this works: +cars = dplyr$tbl_df(cars) +# But this invokes the correct `print` method for class `tbl_df`: +print(cars) +} +} +\seealso{ +\code{\link{unload}} + +\code{\link{reload}} + +\code{\link{module_name}} + +\code{\link{module_help}} +} diff --git a/man/import_search_path.Rd b/man/import_search_path.Rd new file mode 100644 index 00000000..0dadbea7 --- /dev/null +++ b/man/import_search_path.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/find_module.r +\name{import_search_path} +\alias{import_search_path} +\title{Return the import module search path} +\usage{ +import_search_path() +} +\description{ +Return the import module search path +} +\note{ +The search paths are ordered from highest to lowest priority. +The current module’s path always has the lowest priority. + +There are two ways of modifying the module search path: by default, +\code{options('import.path')} specifies the search path. If and only if that +is unset, R also considers the environment variable \code{R_IMPORT_PATH}. +} diff --git a/man/is_S3_user_generic.Rd b/man/is_S3_user_generic.Rd new file mode 100644 index 00000000..7a7b5f13 --- /dev/null +++ b/man/is_S3_user_generic.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/S3.r +\name{is_S3_user_generic} +\alias{is_S3_user_generic} +\title{Check whether a function given by name is a user-defined generic} +\usage{ +is_S3_user_generic(function_name, envir = parent.frame()) +} +\arguments{ +\item{function_name}{function name as character string} + +\item{envir}{the environment this function is invoked from} +} +\description{ +A user-defined generic is any function which, at some point, calls +} diff --git a/man/loaded_modules.Rd b/man/loaded_modules.Rd new file mode 100644 index 00000000..2ed83bfb --- /dev/null +++ b/man/loaded_modules.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\docType{data} +\name{loaded_modules} +\alias{loaded_modules} +\alias{is_module_loaded} +\alias{cache_module} +\alias{uncache_module} +\alias{clear_modules_cache} +\alias{get_loaded_module} +\title{Environment of loaded modules} +\format{An object of class \code{environment} of length 0.} +\usage{ +loaded_modules + +is_module_loaded(module_path) + +cache_module(module_ns) + +uncache_module(module_ns) + +clear_modules_cache() + +get_loaded_module(module_path) +} +\arguments{ +\item{module_path}{fully resolved module path} + +\item{module_ns}{module namespace environment} +} +\description{ +Each module is stored as an environment inside \code{loaded_modules} with +the module’s code location path as its identifier. The path rather than the +module name is used because module names are not unique: two modules called +\code{a} can exist nested inside modules \code{b} and \code{c}, respectively. +Yet these may be loaded at the same time and need to be distinguished. + +\code{is_module_loaded} tests whether a module is already lodaded + +\code{cache_module} caches a module namespace and marks the module as loaded. + +\code{uncache_module} removes a module namespace from the cache, unloading +the module from memory. + +\code{clear_modules_cache} unloads all loaded modules from the cache. + +\code{get_loaded_module} returns a loaded module, identified by its path, +from cache. +} +\keyword{datasets} diff --git a/man/lsf.Rd b/man/lsf.Rd new file mode 100644 index 00000000..49bd8be8 --- /dev/null +++ b/man/lsf.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/S3.r +\name{lsf} +\alias{lsf} +\title{Return a list of functions in an environment} +\usage{ +lsf(envir) +} +\arguments{ +\item{envir}{the environment to search in} +} +\description{ +Return a list of functions in an environment +} diff --git a/man/make_S3_methods_known.Rd b/man/make_S3_methods_known.Rd new file mode 100644 index 00000000..c6f129a5 --- /dev/null +++ b/man/make_S3_methods_known.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/S3.r +\name{make_S3_methods_known} +\alias{make_S3_methods_known} +\title{Find and register S3 methods inside a module} +\usage{ +make_S3_methods_known(module) +} +\arguments{ +\item{module}{the module object for which to register S3 methods} +} +\description{ +Find and register S3 methods inside a module +} diff --git a/man/module_attributes.Rd b/man/module_attributes.Rd new file mode 100644 index 00000000..eeaae93b --- /dev/null +++ b/man/module_attributes.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{module_attributes} +\alias{module_attributes} +\alias{module_attributes<-} +\alias{module_attr} +\alias{module_attr<-} +\title{Module attributes} +\usage{ +module_attributes(module) + +module_attributes(module) <- value + +module_attr(module, attr) + +module_attr(module, attr) <- value +} +\arguments{ +\item{module}{a module} + +\item{value}{the attributes to assign} + +\item{attr}{the attribute name} +} +\description{ +\code{module_attributes} returns or assigns the attributes associated with +a module. + +\code{module_attr} reads or assigns a single attribute associated with a +module. +} diff --git a/man/module_base_path.Rd b/man/module_base_path.Rd new file mode 100644 index 00000000..1163a4e2 --- /dev/null +++ b/man/module_base_path.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{module_base_path} +\alias{module_base_path} +\title{Get a module’s base directory} +\usage{ +module_base_path(module) +} +\arguments{ +\item{module}{a module environment or namespace} +} +\value{ +A character string containing the module’s base directory, + or the current working directory if not invoked on a module. +} +\description{ +Get a module’s base directory +} diff --git a/man/module_file.Rd b/man/module_file.Rd new file mode 100644 index 00000000..f92f84cc --- /dev/null +++ b/man/module_file.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_file.r +\name{module_file} +\alias{module_file} +\title{Find the full file names of files in modules} +\usage{ +module_file(..., module = parent.frame(), mustWork = FALSE) +} +\arguments{ +\item{...}{character vectors of files or subdirectories inside a module; if +none is given, return the root directory of the module} + +\item{module}{a module environment (default: current module)} + +\item{mustWork}{logical; if \code{TRUE}, an error is raised if the given +files do not match existing files.} +} +\value{ +A character vector containing the absolute paths to the files + specified in \code{...}, or an empty string, \code{''}, if no file was + found (unless \code{mustWork = TRUE} was specified). +} +\description{ +Find the full file names of files in modules +} +\note{ +If called from outside a module, the current working directory is used. + +This function is similar to \code{system.file} for packages. It is provided +as a separate function rather than overriding \code{system.file} because that +would cause ambiguity when a module and a package share the same name. +} +\seealso{ +\code{\link[base]{system.file}} +} diff --git a/man/module_init_files.Rd b/man/module_init_files.Rd new file mode 100644 index 00000000..173797df --- /dev/null +++ b/man/module_init_files.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/find_module.r +\name{module_init_files} +\alias{module_init_files} +\title{Return a list of paths to a module’s \code{__init__.r} files} +\usage{ +module_init_files(module, module_path) +} +\arguments{ +\item{module}{character string of the fully qualified module name} + +\item{module_path}{the module’s file path prefix (see \code{Details})} +} +\value{ +a vector of paths to the module’s \code{__init__.r} files, in the +order in which they need to be executed, or \code{NULL} if the arguments do +not resolve to a valid nested module (i.e. not all of the path components +which form the qualified module name contain a \code{__init__.r} file). +The vector’s \code{names} are the names of the respective modules. +} +\description{ +Return a list of paths to a module’s \code{__init__.r} files +} +\details{ +The \code{module_path} is the fully qualified module path, but +without the trailing module file (either \code{x.r} or \code{x/__init__.r}). +} diff --git a/man/module_name.Rd b/man/module_name.Rd new file mode 100644 index 00000000..cf03f9e7 --- /dev/null +++ b/man/module_name.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{module_name} +\alias{module_name} +\title{Get a module’s name} +\usage{ +module_name(module = parent.frame()) +} +\arguments{ +\item{module}{a module environment (default: current module)} +} +\value{ +A character string containing the name of the module or \code{NULL} + if called from outside a module. +} +\description{ +Get a module’s name +} +\note{ +A module’s name is the name of a module that it was \code{import}ed +with. If the same module is subsequently imported using another qualifie +name (from within the same package, say, and hence truncated), the module +names of the two module instances may differ, even though the same copy of +the byte code is used. +This function approximates Python’s magic variable \code{__name__}, and can +be used similarly to test whether a module was loaded via \code{import} or +invoked directly. +} +\examples{ +\dontrun{ +message('This code is always executed.\\n') + +if (is.null(module_name())) { + message('This code is only executed when the module is run + as stand-alone code via Rscript or R CMD BATCH.\\n') +} +} +} diff --git a/man/module_path.Rd b/man/module_path.Rd new file mode 100644 index 00000000..fd2a333a --- /dev/null +++ b/man/module_path.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{module_path} +\alias{module_path} +\title{Get a module’s path} +\usage{ +module_path(module) +} +\arguments{ +\item{module}{a module environment or namespace} +} +\value{ +A character string containing the module’s full path. +} +\description{ +Get a module’s path +} diff --git a/man/modules.Rd b/man/modules.Rd new file mode 100644 index 00000000..9477f287 --- /dev/null +++ b/man/modules.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/modules-package.r +\docType{package} +\name{modules} +\alias{modules} +\alias{modules-package} +\title{An alternative module system for R} +\description{ +Use \code{module = import('module')} to import a module for usage, or +\code{module = import_package('package')} to import a package. Fully +qualified names are supported for nested modules, reminiscent of Python’s +module mechanism. +} +\section{S3 class support}{ + + +Modules can contain S3 generics and methods. To override known generics +(defined outside modules), methods inside a module need to be registered +using \code{\link{register_S3_method}}. See the documentation on that +function for details. +} + +\section{Package options}{ + + +\itemize{ + \item \code{import.path}: + A vector of paths which are searched for modules. The paths are ordered + from highest to lowest precedence – if a module of the same name exists + in two paths, the first hit is accepted. + The current directory is always appended to the search paths. + \item \code{import.attach}: + A logical specifying whether functions from a module should be attached + by default when using \code{import}, even if \code{attach=FALSE} is + specified. This option is only considered while running in interactive + mode, and not inside a module. The option is furthermore overridden by + explicitly passing a value to the \code{attach_operators} argument. + \item \code{import.warn_conflicts}: + A logical specifying whether \code{import} and \code{import_package} + should issue a warning about masked objects when attaching a module to + the global object search path. This warning is only issued while running + in interactive mode. The option defaults to \code{TRUE}. +} +} + +\seealso{ +\code{\link{import}} + +\code{\link{import_package}} + +\code{\link{module_name}} + +\code{\link{module_file}} + +\code{\link{unload}}, \code{\link{reload}} + +\code{\link{export_submodule}} + +\code{\link{register_S3_method}} +} diff --git a/man/register_S3_method.Rd b/man/register_S3_method.Rd new file mode 100644 index 00000000..885a7446 --- /dev/null +++ b/man/register_S3_method.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/S3.r +\name{register_S3_method} +\alias{register_S3_method} +\title{Register an S3 method for a given generic and class.} +\usage{ +register_S3_method(name, class, method) +} +\arguments{ +\item{name}{the name of the generic as a character string} + +\item{class}{the class name} + +\item{method}{the method to register} +} +\description{ +Register an S3 method for a given generic and class. +} +\details{ +Methods for generics defined in the same module do not need to be +registered explicitly, and indeed \emph{should not} be registered. However, +if the user wants to add a method for a known generic (e.g. +\code{\link{print}}), then this needs to be made known explicitly. +} +\note{ +\strong{Do not} call \code{\link{registerS3method}} inside a module. +Use \code{register_S3_method} instead – this is important for the module’s +own book-keeping. +} +\examples{ +\dontrun{ +# In module a: +print.my_class = function (x) { + message(sprintf('My class with field \%s\\n', x$field)) + invisible(x) +} + +register_S3_method('print', 'my_class', print.my_class) + +# Globally: +a = import('a') +obj = structure(list(field = 42), class = 'my_class') +obj # calls `print`, with output "My class with field 42" +} +} diff --git a/man/reload.Rd b/man/reload.Rd new file mode 100644 index 00000000..a82a53f0 --- /dev/null +++ b/man/reload.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{reload} +\alias{reload} +\title{Reload a given module} +\usage{ +reload(module) +} +\arguments{ +\item{module}{reference to the module which should be unloaded} +} +\description{ +Remove the loaded module from the cache, forcing a reload. The newly reloaded +module is assigned to the module reference in the calling scope. +} +\note{ +Any other references to the loaded modules remain unchanged, and will +still work. Reloading modules is primarily useful for testing during +development, and should not be used in production code. + +\code{reload} comes with a few restrictions. It attempts to re-attach itself +in parts or whole if it was previously attached in parts or whole. This only +works if it is called in the same scope as the original \code{import}. +} +\seealso{ +\code{\link{import}} + +\code{\link{unload}} +} diff --git a/man/script_path.Rd b/man/script_path.Rd new file mode 100644 index 00000000..64bf4a25 --- /dev/null +++ b/man/script_path.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{script_path} +\alias{script_path} +\title{Return an R script’s path} +\usage{ +script_path() +} +\description{ +Return an R script’s path +} diff --git a/man/set_script_path.Rd b/man/set_script_path.Rd new file mode 100644 index 00000000..b482a25c --- /dev/null +++ b/man/set_script_path.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_cache.r +\name{set_script_path} +\alias{set_script_path} +\title{Set the base path of the script.} +\usage{ +set_script_path(path) +} +\arguments{ +\item{path}{character string containing the relative or absolute path, or +\code{NULL} to reset the path} +} +\description{ +Set the base path of the script. +} +\details{ +\emph{modules} needs to know the base path of the topmost calling R script +to find relative import locations. In most cases, it can figure the path out +automatically. However, in some cases third party packages load files in such +a way that \emph{modules} cannot find out the correct path of the script any +more. \code{set_script_path} can be used in these cases to set the script +path manually. +} diff --git a/man/split_path.Rd b/man/split_path.Rd new file mode 100644 index 00000000..b6a6398c --- /dev/null +++ b/man/split_path.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/find_module.r +\name{split_path} +\alias{split_path} +\alias{merge_path} +\title{Split a path into its components and merge them back together} +\usage{ +split_path(path) + +merge_path(components) +} +\arguments{ +\item{path}{the path to split} + +\item{components}{character string vector of path components to merge} +} +\value{ +\code{split_path} returns a character vector of path components that +logically represent \code{path}. + +\code{merge_path} returns a single character string that is +logically equivalent to the \code{path} passed to \code{split_path}. +logically represent \code{path}. +} +\description{ +\code{split_path(path)} is a platform independent and file system logic +aware alternative to \code{strsplit(path, '/')[[1]]}. + +\code{merge_path(split_path(path))} is equivalent to \code{path}. +} +\note{ +\code{merge_path} is the inverse function to \code{split_path}. +However, this does not mean that its result will be identical to the +original path. Instead, it is only guaranteed that it will refer to the same +logical path given the same working directory. +} diff --git a/man/unload.Rd b/man/unload.Rd new file mode 100644 index 00000000..e036da73 --- /dev/null +++ b/man/unload.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/import.r +\name{unload} +\alias{unload} +\title{Unload a given module} +\usage{ +unload(module) +} +\arguments{ +\item{module}{reference to the module which should be unloaded} +} +\description{ +Unset the module variable that is being passed as a parameter, and remove the +loaded module from cache. +} +\note{ +Any other references to the loaded modules remain unchanged, and will +still work. However, subsequently importing the module again will reload its +source files, which would not have happened without \code{unload}. +Unloading modules is primarily useful for testing during development, and +should not be used in production code. + +\code{unload} comes with a few restrictions. It attempts to detach itself +if it was previously attached. This only works if it is called in the same +scope as the original \code{import}. +} +\seealso{ +\code{\link{import}} + +\code{\link{reload}} +}