Package-free namespaces

When reorganizing my code base identifier, how to clear my code exchange mechanism. So far, I'm using source for many small, mostly standalone functionality modules.

However, this approach suffers from a number of problems, among them

  • lack of tests for roundness (random circle chains source ),
  • the complex syntax necessary to correctly indicate the included paths (argument chdir=TRUE , hard-coded paths),
  • potential for name conflicts (when redefining objects).

Ideally, Id likes something similar to the Python module engine. The mechanism of the R package will be redundant here: I do not want to generate a hierarchy of nested paths, several files with a lot of metadata and manually create a package to get a small stand-alone reusable module.

Now Im uses a piece of code that allows me to solve the first two problems mentioned above. The inclusion syntax is as follows:

 import(functional) import(io) import(strings) 

... and the module is defined as a simple source file that is in the local path. The definition of import is simple, but I canโ€™t solve the third question: I want to import a module into a separate namespace, but from what I see the namespace search mechanism is pretty tightly connected with packages. True, I could override `::` or getExportedValue and possibly asNamespace and isNamespace , but this is very dirty and can break other packages.

+19
module namespaces r
Apr 03 '13 at
source share
6 answers

Here is a function that fully automates the creation, compilation, and reloading of packages. As others have noted, the package.skeleton() and devtools::load_all() utility functions are almost getting to the end. This simply combines their functionality, using package.skeleton() to create the source directory in the temp directory, which is cleared when load_all() finishes processing it.

All you have to do is specify the source files from which you want to read in the functions and give the package a name: import() does the rest for you.

 import <- function(srcFiles, pkgName) { require(devtools) dd <- tempdir() on.exit(unlink(file.path(dd, pkgName), recursive=TRUE)) package.skeleton(name=pkgName, path = dd, code_files=srcFiles) load_all(file.path(dd, pkgName)) } ## Create a couple of example source files cat("bar <- function() {print('Hello World')}", file="bar.R") cat("baz <- function() {print('Goodbye, cruel world.')}", file="baz.R") ## Try it out import(srcFiles=c("bar.R", "baz.R"), pkgName="foo") ## Check that it worked head(search()) # [1] ".GlobalEnv" "package:foo" "package:devtools" # [4] "package:stats" "package:graphics" "package:grDevices" bar() # [1] "Hello World" foo::baz() # [1] "Goodbye, cruel world." 
+14
Apr 03 '13 at
source share

Conrad, in all seriousness, the answer to the demand

to get a small, stand-alone reusable module

- create a package. This gospel has been repeated many times here, on SO, and elsewhere. In fact, you can create minimal packages with minimal fuzz.

In addition, after launch

  setwd("/tmp") package.skeleton("konrad") 

and deleting one temporary file, I stay with

  edd@max:/tmp$ tree konrad/ konrad/ โ”œโ”€โ”€ DESCRIPTION โ”œโ”€โ”€ man โ”‚  โ””โ”€โ”€ konrad-package.Rd โ””โ”€โ”€ NAMESPACE 1 directory, 3 files edd@max:/tmp$ 

Is it really burdensome?

+15
Apr 03 '13 at 14:02
source share

A package is just an agreement on where to store files (R files in R/ , docs in man/ , compiled code in src , data in data/ ): if you have more than a few files, it is best to stick to the established convention. In other words, using a package is easier than using a package because you donโ€™t have to think: you can just use the existing conventions and each user R will understand what is happening.

For the entire minimum package, a DESCRIPTION file is required, which says what the package does, who can use it (license) and who to contact if there are problems (accompanying). This is a little overhead, but that is not the point. Once you have written this, you simply fill in additional directories as needed - no clumsy package.skeleton() is needed.

However, the built-in tools for working with packages are cumbersome - you need to rebuild / reinstall the package, restart R and reload the package. What is where devtools::load_all() and Rstudio build and reload come in - they use the same specification for the package, but provide easier ways to update the package from the source code. You can, of course, use the code snippets provided by the other answers, but why not use the well-tested code that has been used by hundreds (well, at least tens) of R developers?

+13
Apr 04 '13 at 12:23
source share

My comment on the OP question was not entirely correct, but I think this rewriting of the import function does the trick. foo.R and bar.R are files in the current working directory that contain one function ( baz ), which displays the result shown below.

 import <- function (module) { module <- as.character(substitute(module)) # Search path handling omitted for simplicity. filename <- paste(module, 'R', sep = '.') # create imports environment if it doesn't exist if ("imports" %in% search()) imports <- as.environment(match("imports",search())) # otherwise get the imports environment else imports <- attach(NULL, name="imports") if (module %in% ls("imports")) return() # create a new environment (imports as parent) env <- new.env(parent=imports) # source file into env sys.source(filename, env) # ...and assign env to imports as "module name" assign(module, env, imports) } setwd(".") import(foo) import(bar) foo$baz() # [1] "Hello World" bar$baz() # [1] "Buh Bye" 

Note that baz() alone will not be found, but the OP seemed to still need an explanation :: .

+7
Apr 03 '13 at 15:34
source share

I fully sympathize with @Dirk's answer. The small overhead of creating a minimal package seems to be appropriate for the "standard way."

However, one thing that came to mind is the source local argument, which allows you to enter the source code into the environment , which can be used as a namespace, for example

 assign(module, new.env(parent=baseenv()), envir=topenv()) source(filename, local=get(module, topenv()), chdir = TRUE) 

To access these imported environments using simple syntax, give these import environments a new class (say, โ€œimportโ€) and create :: generic, the default getExportedValue if pkg does not exist.

 import <- function (module) { module <- as.character(substitute(module)) # Search path handling omitted for simplicity. filename <- paste(module, 'R', sep = '.') e <- new.env(parent=baseenv()) class(e) <- 'import' assign(module, e, envir=topenv()) source(filename, local=get(module, topenv()), chdir = TRUE) } '::.import' <- function(env, obj) get(as.character(substitute(obj)), env) '::' <- function(pkg, name) { pkg <- as.character(substitute(pkg)) name <- as.character(substitute(name)) if (exists(pkg)) UseMethod('::') else getExportedValue(pkg, name) } 

Update

The following is a safer option that prevents errors if the downloaded package contains an object with the same name as the package that is accessed using :: .

 '::' <- function(pkg, name) { pkg.chr <- as.character(substitute(pkg)) name.chr <- as.character(substitute(name)) if (exists(pkg.chr)) { if (class(pkg) == 'import') return(get(name.chr, pkg)) } getExportedValue(pkg.chr, name.chr) } 

This will give the correct result, say if you downloaded data.table and subsequently tried to access one of your objects using :: .

+6
Apr 03
source share

Ive implemented a comprehensive solution and published it as a package, .

Internally, modules use a package-like approach; that is, it loads the code inside the selected namespace environment , and then export (= copies) the selected characters in the module environment , which are returned to the user and optionally attached.

The use of the package is described in detail on its website.

+4
Mar 16 '16 at 13:24
source share



All Articles