tho*_*hal 10 environment r r-markdown purrr r-glue
考虑以下Rmarkdown文档:
---\ntitle: "Environments"\nauthor: "Me"\ndate: "2023-01-13"\noutput: html_document\n---\n\n```{r setup}\nlibrary(glue)\nlibrary(purrr)\n```\n\n```{r vars}\na <- 1\nx <- list("`a` has the value: {a}")\n```\n\n```{r works}\nglue(x[[1L]])\n```\n\n```{r does-not-work, error = TRUE}\nmap_chr(x, glue)\n```\nRun Code Online (Sandbox Code Playgroud)\n当使用RStudio\'s knit按钮时,一切都像魅力一样工作,输出如下:
但是,如果我尝试使用自己的环境调用自己的渲染,则会失败:
\n---\ntitle: "Environments"\nauthor: "Me"\ndate: "2023-01-13"\noutput: html_document\n---\n\n```{r setup}\nlibrary(glue)\nlibrary(purrr)\n```\n\n```{r vars}\na <- 1\nx <- list("`a` has the value: {a}")\n```\n\n```{r works}\nglue(x[[1L]])\n```\n\n```{r does-not-work, error = TRUE}\nmap_chr(x, glue)\n```\nRun Code Online (Sandbox Code Playgroud)\n\n所以显然glue在使用时会在环境中绊倒purrr::map。
我如何render使用自己的环境进行调用而不生成此错误?理想情况下,我不想改变它Rmarkdown本身。
有趣的是,如果我把glue自己的function东西包裹起来,工作就会再次顺利:
```\nglue <- function(...) glue::glue(...)\nmap_chr(x, glue)\n```\nRun Code Online (Sandbox Code Playgroud)\n该问题似乎与 无关knitr/rmarkdown,而是一个一般范围问题,似乎与定义所涉及函数的环境有关:
ne <- new.env()\nrender("env.Rmd", envir = ne)\nRun Code Online (Sandbox Code Playgroud)\n
这与 RMarkdown 或 \xe2\x80\x98glue\xe2\x80\x99 无关。它\xe2\x80\x99s也不是一个错误,与我之前声称的相反。事实上,只需访问环境中的变量即可重现该问题e,例如通过以下get函数:
e = env(a = 1)\nlocal(lapply("a", get), envir = e)\n# Error in FUN(X[[i]], ...) : object \'a\' not found\nRun Code Online (Sandbox Code Playgroud)\n这是 R\xe2\x80\x99s词法作用域规则的结果:
\nlapply在其调用框架内执行FUN(= )。1由于 R 作用域的工作方式,2将在其调用作用域中查找变量名称。这个调用范围就是调用框架。当然不存在于 的调用框架中(相比之下,并且存在,因为它们是 的参数名称)。get FUNlapplyaFUNXFUNlapply
如果 R 在本地范围内找不到名称,它将继续在当前环境的父环境中搜索 \xe2\x80\x9cupwards\xe2\x80\x9d。调用框架的父环境是定义函数的环境。在 的情况下lapply,这是namespace:base。
namespace:base 也没有定义名称a,因此搜索继续向上。它的父环境是.GlobalEnv. 3这就是为什么我们在全局环境中定义时lapply("a", get) 会起作用(纯属偶然!) 。4但是,在我们在另一个环境中定义的情况下,永远不会搜索该环境,除非我们将其搜索到搜索路径(当然,\xe2\x80\x99 是一个坏主意)。aaattach()
解决方法是在匿名函数内调用该函数( 或glue,get或任何需要访问局部变量的内容)。严格来说,我们应该始终这样做,而不仅仅是在不同的环境中工作时:
local(lapply("a", \\(.) get(.)), envir = e)\n# [1] "1.000000"\nRun Code Online (Sandbox Code Playgroud)\n这是有效的,因为匿名函数\\(.) get(.)是在调用作用域内定义的,在本例中,调用作用域是e. 因此,在lapply执行该函数时,首先在匿名函数的本地作用域中get搜索名称,没有找到\xe2\x80\x99t ,然后沿着父环境链向上走。第一个父环境是定义匿名函数的环境:。aae
但请注意,我们需要注意参数名称的选择!因为匿名函数的作用域是第一个被搜索的,所以它优先并且可以隐藏我们想要的变量:
\n# Works:\nlocal(lapply("a", \\(.) get(.)), envir = e)\n# [[1]]\n# [1] 1\n\n# Fails:\nlocal(lapply("a", \\(a) get(a)), envir = e)\n# [[1]]\n# [1] "a"\nRun Code Online (Sandbox Code Playgroud)\n1实际上lapply是作为 C 中的内部函数实现的;但为了便于讨论,我们可以假设它在 R 中定义如下:
e = env(a = 1)\nlocal(lapply("a", get), envir = e)\n# Error in FUN(X[[i]], ...) : object \'a\' not found\nRun Code Online (Sandbox Code Playgroud)\n另请注意,我使用的lapply是而不是map_chr,但类似的推理适用于map_chr。
2我想强调的是,R\xe2\x80\x99s 范围规则非常有意义并且内部一致,尽管在这种情况下很不方便。事实上,词法作用域通常优于其他作用域规则。
\n3我\xe2\x80\x99之前曾争论过,这实际上是R 中的一个错误。至少这是一个严重有问题的设计决策,会导致错误和误解,这个问题就是一个很好的例子。因此,我的包 \xe2\x80\x98box\xe2\x80\x99定义了不同的模块环境,专门是为了避免这种行为。
\n4例如(为了说明原始代码实际上只是偶然才起作用!),请考虑以下情况,我们将变量名称更改a为sum:
sum = 1\nlapply("sum", get)\n# [[1]]\n# function (..., na.rm = FALSE) .Primitive("sum")\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 哎呀!get\xe2\x80\x99 并没有返回我们定义的全局变量的值,而是返回 中定义的函数namespace:base,因为namespace:base它位于搜索的父环境链之前。 .GlobalEnv的情况也是如此purrr::map_chr。
| 归档时间: |
|
| 查看次数: |
385 次 |
| 最近记录: |