R: Evaluating a script in an environment

994 views Asked by At

I would like to load a library function within a script evaluated in a specified environment.

Example:

## foo.R
## -----

## blah blah 

library(extrafont)
loadfonts()

Assuming for convenience the evaluation environment is the base environment:

sys.source("foo.R")
## Registering fonts with R
## Error in eval(expr, envir, enclos) : could not find function "loadfonts"

Replacing loadfonts() with extrafont:::loadfonts() works better, but still gives:

Error in get(as.character(FUN), mode = "function", envir = envir) : 
  object 'pdfFonts' of mode 'function' was not found

because loadfonts() requires pdfFonts() defined in grDevices.

1

There are 1 answers

0
antonio On BEST ANSWER

This is both a not totally satisfactory answer and a long comment to @waterling.

The proposed solution is:

e<- new.env() 
source("foo.R", local=e)

i.e.

source("foo.R", local=new.env())

which is substantially equivalent to:

sys.source("foo.R", envir=new.env())

It works for much the same reason why:

sys.source("foo.R", envir=as.environment("package:grDevices"))

As reported in the error (see question), the function not found, pdfFonts() is part of the package grDevices The sys.source above executes the script in the package:grDevices environment, hence the function is found. Instead by default sys.source(..., envir=baseenv()) and the base environment comes before grDevices, therefore pdfFonts() is not found.

A first problem is that I do not know in advance which functions will happen to be in my script. In this case setting envir=new.env() is a more general approach. By default new.env(parent=parent.frame()), therefore it has the same parent of sys.source(), which is the global environment. So everything visible in the global environment is visible in the script with sys.source(..., envir=new.env()), that is every object created by the user and by the user loaded packages.

The problem here is that we are not insulating the script any more, which makes it less reproducible and stable. In fact, it depends on what is in R memory in the very moment we call sys.source. To make the things more practical, it means foo.R might work just because we usually call it after bar.R.

A second problem is that this not an actual solution. The question concerns how to run a script foo.R in an environment e and still access, when needed, to functions not belonging to e. Taking an e that (directly or through its parents) has access to these functions is actually a workaround, not a solution.

If this type of workaround is the only possible way to go, IMHO, the best is to make it dependent only on standard R packages.
At start, R shows:

search()
## [1] ".GlobalEnv"        "package:stats"     "package:graphics" 
## [4] "package:grDevices" "package:utils"     "package:datasets" 
## [7] "package:methods"   "Autoloads"         "package:base"     

that is eight official packages/environments.
New packages/environments, unless explicitly changing the default, go into the second slot and all those after the first one shift one position.

myEnv=new.env()
attach(myEnv)
search()
##  [1] ".GlobalEnv"        "myEnv"             "package:stats"    
##  [4] "package:graphics"  "package:grDevices" "package:utils"    
##  [7] "package:datasets"  "package:methods"   "Autoloads"        
## [10] "package:base"     

So we can take the last eight in the search path, which means taking the first of these eight inheriting the others. We need:

pos.to.env(length(search()) - 7)
## <environment: package:stats>
## attr(,"name")
## [1] "package:stats"
## attr(,"path")
## [1] "path/to//R/R-x.x.x/library/stats"

Therefore:

sys.source("foo.R", envir=new.env(parent=pos.to.env(length(search()) - 7)))

or one can take a standard R reference package, say stats, and its parents.

Therefore:

sys.source("foo.R", envir=new.env(parent=as.environment("package:stats")))

UPDATE

I found the

SOLUTION

As for the script:

#foo.R
#-----
library(extrafont)
f=function() loadfonts()
environment(f) = as.environment("package:extrafont")
f()

To execute in a new environment:

sys.source("foo.R", envir=new.env(parent=baseenv()))

f() now has access to all objects in the package extrafont and those loaded before it.

In sys.source() creating a new.env() with whatever parent is necessary to make environment() assignment work.