[英]Use of glue in map in an RMarkdown in a new environment
考虑以下Rmarkdown
文档:
---
title: "Environments"
author: "Me"
date: "2023-01-13"
output: html_document
---
```{r setup}
library(glue)
library(purrr)
```
```{r vars}
a <- 1
x <- list("`a` has the value: {a}")
```
```{r works}
glue(x[[1L]])
```
```{r does-not-work, error = TRUE}
map_chr(x, glue)
```
使用RStudio's
knit button 时,一切都很顺利,output 如下所示:
但是,如果我尝试使用自己的环境调用 render 自己,则会失败:
ne <- new.env()
render("env.Rmd", envir = ne)
因此,当在purrr::map
中使用时,显然glue
会在环境中绊倒。
如何在不产生此错误的情况下使用自己的环境调用render
? 理想情况下,我不想更改Rmarkdown
本身。
有趣的是,如果我将glue
包裹在自己的function
中,事情就会再次顺利进行:
```
glue <- function(...) glue::glue(...)
map_chr(x, glue)
```
这个问题似乎与knitr/rmarkdown
,而是一个一般的范围界定问题,似乎与定义所涉及功能的环境有关:
library(rlang)
library(purrr)
library(glue)
rm(list = ls())
e <- env(a = 1, x = "`a` has the value: {a}")
delayedAssign("res", map_chr(x, glue), e, e)
e$res
# Error:
# ℹ In index: 1.
# Caused by error:
# ! object 'a' not found
## as opposed to
a <- 1
x <- "`a` has the value: {a}"
delayedAssign("res", {
map_chr(x, glue)
})
res
# [1] "`a` has the value: 1"
这与 RMarkdown 或“胶水”无关。 这也不是错误,这与我之前声称的相反。 事实上,这个问题可以通过简单地访问环境e
中的一个变量来重现,例如通过get
function:
e = env(a = 1)
local(map_chr("a", get), envir = e)
# Error in .f(.x[[i]], ...) : object 'a' not found
这是 R 的词法范围规则的结果:
lapply
在其调用框架内执行FUN
。 1由于 R 作用域的工作方式, 2 FUN
将在其调用 scope 中查找变量名称。此调用 scope 是lapply
调用框架。 当然a
在FUN
的调用框架中不存在(相比之下, X
和FUN
存在,因为它们是lapply
的参数名称)。
如果 R 在本地 scope 中没有找到名称,则继续“向上”查找,在当前环境的父环境中查找。 调用框架的父环境是定义function 的环境。 对于lapply
,这是namespace:base
。
namespace:base
也没有定义名称a
,因此继续向上搜索。 它的父环境是.GlobalEnv
。 3这就是为什么lapply("a", get)
起作用的原因(纯属偶然!)如果我们在全局环境中定义a
。 然而,在我们的例子中,我们在另一个环境中定义a
一个环境,这个环境永远不会被搜索,除非我们将它attach()
到搜索路径(但这当然是一个坏主意)。
解决方法是在 function 中调用 function( glue
或get
,或任何需要访问局部变量的方法)。严格来说,我们应该始终这样做,而不仅仅是在不同的环境中工作时):
local(map_chr("a", \(.) get(.)), envir = e)
# [1] "1.000000"
这是有效的,因为匿名 function \(.) get(.)
是在调用 scope 中定义的,在这个例子中,它是e
。 所以当lapply
执行这个 function 时,它首先搜索它自己的 scope,没有a
,然后沿着父环境链向上走。 第一个父环境是定义匿名 function 的环境: e
。
但是请注意,我们需要注意参数名称的选择,因为匿名 function 的 scope 是第一个被搜索的:它具有优先权并且可以隐藏我们想要的变量:
e = env(a = 1, x = "`a` has the value: {a}")
# Works:
local(lapply(x, \(v) glue(v)), envir = e)
# [[1]]
# `a` has the value: 1
# Fails:
local(lapply(x, \(a) glue(a)), envir = e)
# [[1]]
# `a` has the value: `a` has the value: {a}
1实际上lapply
在C中是作为内部function实现的; 但为了便于讨论,我们可以假设它在 R 中定义如下:
lapply = function (X, FUN, ...) {
FUN = match.fun(FUN)
if (!is.vector(X) || is.object(X)) X = as.list(X)
res = vector('list', length(X))
for (i in seq_along(X)) res[[i]] = FUN(X[[i]], ...)
res
}
2我想强调的是,R 的范围规则非常合理并且在内部是一致的,即使在这种情况下不方便。 事实上,词法范围通常优于其他范围规则。
3我之前曾争论过,这实际上是R中的一个错误。 至少这是一个严重有问题的设计决策,会导致错误和误解,这个问题就是一个很好的例子。 出于这个原因,我的 package 'box'以不同的方式定义了模块环境, 特别是为了避免这种行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.