Как мне избежать eval и разобрать?

Я написал функцию, которая создает исходные файлы, содержащие сценарии для других функций, и сохраняет эти функции в альтернативной среде, чтобы они не загромождали глобальную среду. Код работает, но содержит три экземпляра 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")
}

Много было написано о неоптимальности конструкции eval(parse(...)) (см. Здесь и здесь). Тем не менее, обсуждения, которые я нашел, в основном касаются альтернативных стратегий для поднабора. Первый и третий экземпляры eval(parse(...)) в моем коде не включают поднаборы (второй экземпляр может быть связан с поднабором).

Есть ли способ вызвать new.env(...), [environment name]$[function name] <- [function name] и attach(...) не прибегая к eval(parse(...))? Благодарю.

NB. Я не хочу менять имена своих функций на .name чтобы скрыть их в глобальной среде.

Ответы

Ответ 1

Для чего бы то ни было, source функции на самом деле использует eval(parse(...)), хотя и несколько тонким способом. Во-первых, .Internal(parse(...)) используется для создания выражений, которые после дальнейшей обработки передаются в eval. Так что eval(parse(...)) кажется достаточно хорошим для основной команды R в этом случае.

Тем не менее, вам не нужно прыгать через обручи к исходным функциям в новой среде. source предоставляет local аргумент, который можно использовать именно для этого.

local: TRUE, FALSE или среда, определяющая, где анализируются выражения.

Пример:

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

тестирование работает:

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

И пример файла test.r чтобы использовать это на:

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

Ответ 2

Если вы хотите оставить это вне global_env, поместите его в пакет. Люди из сообщества R обычно помещают кучу часто используемых вспомогательных функций в свой личный пакет.

Ответ 3

tl; dr: правильный способ преобразования строк в кавычках в имена объектов - это использовать assign() и get(). Смотрите этот пост.

Длинный ответ: Ответ @dww о возможности source() напрямую в конкретную среду привел меня к изменению второго экземпляра eval(parse(...)) следующим образом:

# old version
source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
eval(parse(text = paste0(environment, "$", func," <- ", func)))
# new version
source(
    paste0("C:/Users/JT/R/Functions/", func, ".R"), 
    local = get(environment)
)

Ответ от @dww также заставил меня исследовать attach(). attach() есть аргумент, который позволяет указать среду, в которую следует направить вывод. Это привело меня к изменению третьего экземпляра eval(parse(...)) (ниже). Обратите внимание на использование get() для преобразования "env" которое приходит из environment в env, не заключенный в кавычки, которого требует attach().

# old version
eval(parse(text = paste0("attach(", environment, ")")))
# new version
attach(get(environment), name = environment)

Наконец, в какой-то момент этого процесса мне напомнили, что у rm() есть аргумент character.only. detach() принимает тот же аргумент, поэтому я изменил второй экземпляр eval(parse()) как показано ниже:

# old version
eval(parse(text = paste0("detach(", environment, ")")))
# new version
detach(environment, character.only = T)

Итак, мой новый код:

# 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
            detach(environment, character.only = T)
            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"), 
            local = get(environment)
        )
        cat(func, "created in", environment, "\n")
    })
    attach(get(environment), name = environment)
    cat("attached", environment, "\n\n")
}