Search code examples
reval

How do I avoid eval and parse?


I have written a function that sources files that contain scripts for other functions and stores these functions in an alternative environment so that they aren't cluttering up the global environment. The code works, but contains three instances of eval(parse(...)):

# sourceFunctionHidden ---------------------------
# source a function and hide the function from the global environment
sourceFunctionHidden <- function(functions, environment = "env", ...) {
    if (environment %in% search()) {
        while (environment %in% search()) {
            if (!exists("counter", inherits = F)) counter <- 0
            eval(parse(text = paste0("detach(", environment, ")")))
            counter <- counter + 1 
        }
        cat("detached", counter, environment, "s\n")
    } else {cat("no", environment, "attached\n")}
    if (!environment %in% ls(.GlobalEnv, all.names = T)) {
        assign(environment, new.env(), pos = .GlobalEnv)
        cat("created", environment, "\n")
    } else {cat(environment, "already exists\n")}
    sapply(functions, function(func) {
        source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
        eval(parse(text = paste0(environment, "$", func," <- ", func)))
        cat(func, "created in", environment, "\n")
    })
    eval(parse(text = paste0("attach(", environment, ")")))
    cat("attached", environment, "\n\n")
}

Much has been written about the sub-optimality of the eval(parse(...)) construction (see here and here). However, the discussions that I've found mostly deal with alternate strategies for subsetting. The first and third instances of eval(parse(...)) in my code don't involve subsetting (the second instance might be related to subsetting).

Is there a way to call new.env(...), [environment name]$[function name] <- [function name], and attach(...) without resorting to eval(parse(...))? Thanks.

N.B.: I don't want to change the names of my functions to .name to hide them in the global environment


Solution

  • For what its worth, the function source actually uses eval(parse(...)), albeit in a somewhat subtle way. First, .Internal(parse(...)) is used to create expressions, which after more processing are later passed to eval. So eval(parse(...)) seems to be good enough for the R core team in this instance.

    That said, you don't need to jump through hoops to source functions into a new environment. source provides an argument local that can be used for precisely this.

    local: TRUE, FALSE or an environment, determining where the parsed expressions are evaluated.

    An example:

    env = new.env()
    source('test.r', local = env)
    

    testing it works:

    env$test('hello', 'world')
    # [1] "hello world"
    ls(pattern = 'test')
    # character(0)
    

    And an example test.r file to use this on:

    test = function(a,b) paste(a,b)