【发布时间】:2021-06-08 22:40:06
【问题描述】:
我正在尝试创建一个用 R 编写的玩具 R REPL(here's 源代码)。理想情况下,我希望 REPL 在 R 终端本身中运行,但既不干扰也不依赖于已经在全球环境中评估过的任何东西。不幸的是,我还没有想出解决这个问题的办法。我面临的主要挑战之一是如何附加包环境。
根据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。是否可以将其转换为我们可以轻松测试的功能?
-
你会在这里得到双向的影响——在外面做的事情会影响到里面发生的事情,反之亦然。避免这种情况的常用方法是to run in a separate process,因此您每次都会获得一个新的、孤立的 R 实例。
-
嗨@MrFlick,你绝对是对的。我添加了一个简单的 REPL 来测试这个问题。
-
@alistaire 是的,我知道有双向的影响,可能运行一个单独的进程是防止这种影响的好方法,但我想保持相同的进程,如果可能的。无论如何,感谢您直接联系 callr,我会检查代码,看看它是否带来了一些好的想法。
-
library()和公司有一个pos参数,允许您以不同的顺序附加包/命名空间,但是在 R 会话中只有一个搜索路径和一组加载的命名空间。包不仅仅是环境;还有更多的事情发生。您可以尝试覆盖并复制基础包或在退出时进行清理,但这两条路线都令人担忧。还要留意环境变量和选项之类的东西,它们会面临类似的问题。
标签: r package parent environment read-eval-print-loop