简体   繁体   English

是否可以在 R 中将 package 环境作为特定环境而不是全局环境的父级附加?

[英]Is it possible to attach package environments as parents of a specific environment, not the global environment, in R?

I'm trying to create a toy R REPL written in R ( here's the source code).我正在尝试创建一个用 R 编写的玩具 R REPL(是源代码)。 Ideally I'd like the REPL to run in an R terminal itself, but neither to interfere nor to depend on anything that has already been evaluated at the global environment.理想情况下,我希望 REPL 在 R 终端本身中运行,但既不干涉也不依赖任何已经在全球环境中评估过的东西。 Unfortunately, I haven't been able to do come up with a solution to this problem yet.不幸的是,我还没有想出解决这个问题的办法。 One of the main challenges I'm facing refers to how package environments are attached.我面临的主要挑战之一是如何连接 package 环境。

According to Hadley's Advanced R , packages attached by library() and require() become parents of the global environment.根据Hadley 的 Advanced Rlibrary()require()附加的包成为全局环境的父母。 This implies, however, that if I attach a package inside my toy REPL it will become the parent of the global environment even I'm not running it on the global environment.然而,这意味着如果我在我的玩具 REPL 中附加一个 package ,即使我没有在全局环境中运行它,它也会成为全局环境的父级。

For example (please note that the R> prompt is the "normal" R terminal, and that >>>> is my REPL's "terminal"):例如(请注意, 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"  

We can see that I could use the gtfsio ' import_gtfs() function (the path is missing, but you get the point), but the package was also attached to the "main" R terminal.我们可以看到我可以使用gtfsio ' import_gtfs() function ( path丢失,但你明白了),但 package 也连接到“主” ZE1E1D603D405C731827 终端。 If I try to use another environment as the parent to my new env, I won't even have access to the package's functions, because it won't be able to find them, since the package environment becomes the parent of the global environment, and not of my new environment:如果我尝试使用另一个环境作为新环境的父环境,我什至无法访问包的功能,因为它无法找到它们,因为 package 环境成为全局环境的父环境,而不是我的新环境:

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"     

So, is there any way of attaching package environments as parents of a custom environment, instead of the global environment?那么,有没有办法将 package 环境附加为自定义环境的父级,而不是全局环境? If there's not, is there any way to workaround this issue?如果没有,有没有办法解决这个问题?

Cheers!干杯!


Edit:编辑:

Sorry, I should have given more details on how the REPL works.抱歉,我应该详细说明 REPL 的工作原理。

Basically, I just read the user input using readline() , parse it as an expression and evaluate it in the specified environment.基本上,我只是使用readline()读取用户输入,将其解析为表达式并在指定环境中对其进行评估。 The code below should work for a simple demonstration:下面的代码应该适用于一个简单的演示:

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)
      
  }
    
}

The code I linked above to my GitHub is a bit more complex to handle some conditions, but still, this is the basic idea.我上面链接到我的 GitHub 的代码在处理某些条件时有点复杂,但这仍然是基本思想。

A library() call would then be evaluated as eval(expression(library(gtfsio)), envir = env) .然后, library()调用将被评估为eval(expression(library(gtfsio)), envir = env)

You have only one search path so there's no way to properly attach to another one.您只有一个搜索路径,因此无法正确附加到另一个搜索路径。

You can still have a chain of parent environments though, we might redefine library in your repl_env to set up this chain您仍然可以拥有父环境链,我们可能会在您的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"  

Use repl_env <- new.env(parent = parent.env(.GlobalEnv)) as your first line if you don't want to have access to the global environment's objects.如果您不想访问全局环境的对象,请使用repl_env <- new.env(parent = parent.env(.GlobalEnv))作为第一行。

It will never be 100% robust however, it was a fun exercise but think carefully before doing something serious with this.然而,它永远不会 100% 健壮,这是一个有趣的练习,但在认真对待它之前要仔细考虑。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM