R load libraries only in the local scope

796 Views Asked by At

is it possible to have library() influence only the local scope?

For example, my script has a lapply loop that source() code in other scripts; these scripts load their own libraries, but I would like the namespace to be cleaned after the code in these scripts is executed, so that function in package A called by script 1 do not mask functions in package B called by script 2.

I know there are complete solutions like the modules and import package, but I would like a simpler base R solution first to scale up only if needed.

1

There are 1 best solutions below

5
On BEST ANSWER

Using loadNamespace("pkg") will load the namespace of the 'pkg' package. Using library("pkg") will also attach the 'pkg' package to R's search path. You can see which namespaces are loaded using loadedNamespaces() and which packages are attached using search(). These properties are global to R. In other word, it is not really possible to make them local.

If you wish to use library() inside your function/map-reduce calls without library() causing the package to be attached in your current R session, then, as others suggested, you could evaluate your calls in an external R process.

(disclaimer: I'm the author) I propose to use futures (future) for this where you evaluate them external via future.callr - a future wrapper for callr. The future framework will take care of exporting objects needed to the external R process. Here's an example:

library(future)
plan(future.callr::callr, workers = 1)
y <- lapply((1:3)/4, FUN = function(x) value(future({
  library(gtools)
  logit(x)
})))

Note how a future is created and its value is immediately retrieved, i.e. value(future(...)). To clarify this fact, you could use:

eval_via_future <- function(expr, substitute = TRUE, envir = parent.frame()) {
  if (substitute) expr <- substitute(expr)
  f <- future::future(expr, substitute = FALSE, envir = envir)
  future::value(f)
}
library(future)
plan(future.callr::callr, workers = 1)
y <- lapply((1:3)/4, FUN = function(x) eval_via_future({
  library(gtools)
  logit(x)
})))

If you want to avoid having the user specify the plan(), the you do:

eval_via_callr <- function(expr, substitute = TRUE, envir = parent.frame()) {
  oplan <- future::plan()
  on.exit(future::plan(oplan))
  future::plan(future.callr::callr, workers = 1)
  if (substitute) expr <- substitute(expr)
  f <- future::future(expr, substitute = FALSE, envir = envir)
  future::value(f)
}

so you can do:

y <- lapply((1:3)/4, FUN = function(x) eval_via_callr({
  library(gtools)
  logit(x)
}))

without loading or attaching gtools (it obviously loads other packages needed by future and callr);

> loadedNamespaces()
 [1] "codetools"    "grDevices"    "listenv"      "future"       "ps"          
 [6] "memuse"       "clisymbols"   "prompt"       "digest"       "crayon"      
[11] "rappdirs"     "R6"           "future.callr" "datasets"     "utils"       
[16] "callr"        "graphics"     "base"         "tools"        "parallel"    
[21] "compiler"     "processx"     "stats"        "globals"      "methods"     

> search()
 [1] ".GlobalEnv"        "package:stats"     "package:graphics" 
 [4] "package:grDevices" "package:utils"     "package:datasets" 
 [7] "CBC tools"         "toolbox:default"   "package:methods"  
[10] "Autoloads"         "package:base" 

UPDATE 2020-07-26

Here's how to source R scripts in an external R process via a future while at the same time pulling globals from the main R session:

future_source <- function(file, envir = parent.frame(), ...) {
  expr <- parse(file = file, keep.source = FALSE)
  expr <- bquote({..(expr)}, splice = TRUE)
  future::future(expr, substitute = FALSE, envir = envir, ...)
}

source_via_callr <- function(file, envir = parent.frame()) {
  oplan <- future::plan()
  on.exit(future::plan(oplan))
  future::plan(future.callr::callr, workers = 1)
  f <- future_source(file, envir = envir)
  future::value(f)
}
## Two R scripts
cat("log(a)\n", file="a.R")
cat("library(gtools)\nlogit(a)\n", file="b.R")

## A global
a <- 0.42

## Source scripts in external process
y <- lapply(c("a.R", "b.R"), FUN = source_via_callr)