是否可以在 R 中将包环境附加为特定环境而不是全局环境的父级?
Is it possible to attach package environments as parents of a specific environment, not the global environment, in R?
我正在尝试创建一个用 R 编写的玩具 R REPL(here's 源代码)。理想情况下,我希望 R 终端本身的 REPL 为 运行,但既不干扰也不依赖于已在全局环境中评估过的任何内容。不幸的是,我还没有想出解决这个问题的方法。我面临的主要挑战之一是如何附加包环境。
根据 Hadley's Advanced R,library()
和 require()
附加的包成为全局环境的父级。然而,这意味着如果我在我的玩具 REPL 中附加一个包,即使我没有 运行 将它连接到全局环境,它也会成为全局环境的父级。
例如(请注意 R>
提示符是“普通”R 终端,而 >>>>
是我的 REPL 的“终端”):
R> search()
# [1] ".GlobalEnv" "tools:rstudio" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:methods" "Autoloads"
# [10] "package:base"
R> replr::replr(env = new.env()) # new.env() defaults to having global environment as a parent
>>>> library(gtfsio)
>>>> rlang::env_parents(last = emptyenv())
# [[1]] $ <env: global>
# [[2]] $ <env: package:gtfsio>
# [[3]] $ <env: tools:rstudio>
# [[4]] $ <env: package:stats>
# [[5]] $ <env: package:graphics>
# [[6]] $ <env: package:grDevices>
# [[7]] $ <env: package:utils>
# [[8]] $ <env: package:datasets>
# [[9]] $ <env: package:methods>
# [[10]] $ <env: Autoloads>
# [[11]] $ <env: package:base>
# [[12]] $ <env: empty>
>>>> import_gtfs()
# Error: argument "path" is missing, with no default
>>>> q()
R> search()
# [1] ".GlobalEnv" "package:gtfsio" "tools:rstudio"
# [4] "package:stats" "package:graphics" "package:grDevices"
# [7] "package:utils" "package:datasets" "package:methods"
# [10] "Autoloads" "package:base"
我们可以看到我可以使用 gtfsio
' import_gtfs()
函数(缺少 path
,但你明白了),但包也附加到“主要”R 终端。如果我尝试使用另一个环境作为我的新环境的父级,我什至无法访问包的功能,因为它无法找到它们,因为包环境成为全局环境的父级,而不是我的新环境:
Restarting R session...
R> search()
# [1] ".GlobalEnv" "tools:rstudio" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:methods" "Autoloads"
# [10] "package:base"
R> replr::replr(env = new.env(parent = baseenv()))
>>>> library(gtfsio)
>>>> rlang::env_parents()
# [[1]] $ <env: package:base>
# [[2]] $ <env: empty>
>>>> import_gtfs()
# Error: could not find function "import_gtfs"
>>>> q()
R> search()
# [1] ".GlobalEnv" "package:gtfsio" "tools:rstudio"
# [4] "package:stats" "package:graphics" "package:grDevices"
# [7] "package:utils" "package:datasets" "package:methods"
# [10] "Autoloads" "package:base"
那么,有没有什么方法可以将包环境附加为自定义环境的父级,而不是全局环境?如果没有,是否有任何方法可以解决此问题?
干杯!
编辑:
抱歉,我应该提供更多关于 REPL 工作原理的细节。
基本上,我只是使用 readline()
读取用户输入,将其解析为表达式并在指定环境中对其求值。下面的代码应该可以用于简单的演示:
simple_repl <- function(env = new.env()) {
while (TRUE) {
input <- readline(">>>> ")
if (input == "q()") break
expr <- parse(text = input)
result <- withVisible(
eval(expr, envir = env)
)
if (result$visible)
print(result$value)
}
}
我上面链接到我的 GitHub 的代码在处理某些情况时有点复杂,但这仍然是基本思想。
library()
调用将被评估为 eval(expression(library(gtfsio)), envir = env)
。
您只有一个搜索路径,因此无法正确附加到另一个搜索路径。
尽管如此,您仍然可以拥有父环境链,我们可能会在您的 repl_env
中重新定义 library
以设置此链
repl_env <- new.env()
with(repl_env, library <- function(package) {
# fetch repl_env from the inside
repl_env <- parent.env(environment())
# and its parent (.GlobalEnv the first time)
parent_env <- parent.env(repl_env)
# create a new env for our package and fill it
pkg_env <- new.env()
package <- deparse1(substitute(package))
object_nms <- getNamespaceExports(package)
objects <- mget(object_nms, envir = asNamespace(package))
list2env(objects, pkg_env)
# stitch it above repl_env and below repl_env's parent
parent.env(pkg_env) <- parent_env
parent.env(repl_env) <- pkg_env
# base::library returns the search path invisibly but here it woudn't make
# sense so we just return NULL
invisible(NULL)
})
simple_repl(repl_env)
>>>> x <- "hello"
>>>> y <- "world"
>>>> library(glue)
>>>> glue("{x} {y}")
#> hello world
>>>>
# the {glue} package is not on the search path
search()
#> [1] ".GlobalEnv" "tools:rstudio" "package:stats" "package:graphics"
#> [5] "package:grDevices" "package:utils" "package:datasets" "package:methods"
#> [9] "Autoloads" "package:base"
如果您不想访问全局环境的对象,请使用 repl_env <- new.env(parent = parent.env(.GlobalEnv))
作为第一行。
它永远不会 100% 稳健,但是,这是一个有趣的练习,但在做一些严肃的事情之前请仔细考虑。
我正在尝试创建一个用 R 编写的玩具 R REPL(here's 源代码)。理想情况下,我希望 R 终端本身的 REPL 为 运行,但既不干扰也不依赖于已在全局环境中评估过的任何内容。不幸的是,我还没有想出解决这个问题的方法。我面临的主要挑战之一是如何附加包环境。
根据 Hadley's Advanced R,library()
和 require()
附加的包成为全局环境的父级。然而,这意味着如果我在我的玩具 REPL 中附加一个包,即使我没有 运行 将它连接到全局环境,它也会成为全局环境的父级。
例如(请注意 R>
提示符是“普通”R 终端,而 >>>>
是我的 REPL 的“终端”):
R> search()
# [1] ".GlobalEnv" "tools:rstudio" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:methods" "Autoloads"
# [10] "package:base"
R> replr::replr(env = new.env()) # new.env() defaults to having global environment as a parent
>>>> library(gtfsio)
>>>> rlang::env_parents(last = emptyenv())
# [[1]] $ <env: global>
# [[2]] $ <env: package:gtfsio>
# [[3]] $ <env: tools:rstudio>
# [[4]] $ <env: package:stats>
# [[5]] $ <env: package:graphics>
# [[6]] $ <env: package:grDevices>
# [[7]] $ <env: package:utils>
# [[8]] $ <env: package:datasets>
# [[9]] $ <env: package:methods>
# [[10]] $ <env: Autoloads>
# [[11]] $ <env: package:base>
# [[12]] $ <env: empty>
>>>> import_gtfs()
# Error: argument "path" is missing, with no default
>>>> q()
R> search()
# [1] ".GlobalEnv" "package:gtfsio" "tools:rstudio"
# [4] "package:stats" "package:graphics" "package:grDevices"
# [7] "package:utils" "package:datasets" "package:methods"
# [10] "Autoloads" "package:base"
我们可以看到我可以使用 gtfsio
' import_gtfs()
函数(缺少 path
,但你明白了),但包也附加到“主要”R 终端。如果我尝试使用另一个环境作为我的新环境的父级,我什至无法访问包的功能,因为它无法找到它们,因为包环境成为全局环境的父级,而不是我的新环境:
Restarting R session...
R> search()
# [1] ".GlobalEnv" "tools:rstudio" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:methods" "Autoloads"
# [10] "package:base"
R> replr::replr(env = new.env(parent = baseenv()))
>>>> library(gtfsio)
>>>> rlang::env_parents()
# [[1]] $ <env: package:base>
# [[2]] $ <env: empty>
>>>> import_gtfs()
# Error: could not find function "import_gtfs"
>>>> q()
R> search()
# [1] ".GlobalEnv" "package:gtfsio" "tools:rstudio"
# [4] "package:stats" "package:graphics" "package:grDevices"
# [7] "package:utils" "package:datasets" "package:methods"
# [10] "Autoloads" "package:base"
那么,有没有什么方法可以将包环境附加为自定义环境的父级,而不是全局环境?如果没有,是否有任何方法可以解决此问题?
干杯!
编辑:
抱歉,我应该提供更多关于 REPL 工作原理的细节。
基本上,我只是使用 readline()
读取用户输入,将其解析为表达式并在指定环境中对其求值。下面的代码应该可以用于简单的演示:
simple_repl <- function(env = new.env()) {
while (TRUE) {
input <- readline(">>>> ")
if (input == "q()") break
expr <- parse(text = input)
result <- withVisible(
eval(expr, envir = env)
)
if (result$visible)
print(result$value)
}
}
我上面链接到我的 GitHub 的代码在处理某些情况时有点复杂,但这仍然是基本思想。
library()
调用将被评估为 eval(expression(library(gtfsio)), envir = env)
。
您只有一个搜索路径,因此无法正确附加到另一个搜索路径。
尽管如此,您仍然可以拥有父环境链,我们可能会在您的 repl_env
中重新定义 library
以设置此链
repl_env <- new.env()
with(repl_env, library <- function(package) {
# fetch repl_env from the inside
repl_env <- parent.env(environment())
# and its parent (.GlobalEnv the first time)
parent_env <- parent.env(repl_env)
# create a new env for our package and fill it
pkg_env <- new.env()
package <- deparse1(substitute(package))
object_nms <- getNamespaceExports(package)
objects <- mget(object_nms, envir = asNamespace(package))
list2env(objects, pkg_env)
# stitch it above repl_env and below repl_env's parent
parent.env(pkg_env) <- parent_env
parent.env(repl_env) <- pkg_env
# base::library returns the search path invisibly but here it woudn't make
# sense so we just return NULL
invisible(NULL)
})
simple_repl(repl_env)
>>>> x <- "hello"
>>>> y <- "world"
>>>> library(glue)
>>>> glue("{x} {y}")
#> hello world
>>>>
# the {glue} package is not on the search path
search()
#> [1] ".GlobalEnv" "tools:rstudio" "package:stats" "package:graphics"
#> [5] "package:grDevices" "package:utils" "package:datasets" "package:methods"
#> [9] "Autoloads" "package:base"
如果您不想访问全局环境的对象,请使用 repl_env <- new.env(parent = parent.env(.GlobalEnv))
作为第一行。
它永远不会 100% 稳健,但是,这是一个有趣的练习,但在做一些严肃的事情之前请仔细考虑。