从一个环境注入表达式并在另一个环境中评估
Inject Expression from One Environment and Evaluate in Another
更新
事实证明rlang::expr_interp()
这个函数基本上达到了我的目的
unquo_2 <- function(expr, inj_env = rlang::caller_env(), eval_env = NULL) {
# Capture verbatim the argument passed to 'expr'...
expr_quo <- rrlang::enquo0(expr)
# ...and extract it literally as an expression.
expr_lit <- rlang::quo_get_expr(expr_quo)
# Unquote that expression in the context of the injection environment.
expr_inj <- rlang::expr_interp(expr_lit, inj_env)
# As desired, return either the unquoted expression itself...
if(rlang::is_null(eval_env)) {
expr_inj
}
# ...or the result of evaluating it in the evaluation environment.
else {
rlang::eval_bare(expr_inj, eval_env)
}
}
不幸的是,expr_interp()
是 deprecated in favor of inject()
,它既不适合单独的注入环境,也不适合在评估之前 return 表达式。
目标
我正在开发一个包,我需要一个行为类似于 rlang::inject()
or rlang::qq_show()
的函数。此函数的形式应为
unquo <- function(expr, inj_env) {
# ...
}
接受表达式 expr
并注入来自 inj_env
的参数。然后它 return 是注入的表达式本身, 没有 评估它。
例如:
library(rlang)
# Arguments to inject from global environment.
a <- sym("a.global")
b <- sym("b.global")
# Arguments to inject from custom environment, which has no parent.
my_env <- new_environment(list(a = sym("a.custom")))
# Injecting from global environment.
unquo(!!a + !!b, global_env())
#> a.global + b.global
# Injecting from custom environment...
unquo(!!a + 1, my_env)
#> a.custom + 1
# ...where 'b' neither exists nor is inherited.
unquo(!!a + !!b, my_env)
#> Error in enexpr(expr) : object 'b' not found
障碍
不幸的是,inject()
和 qq_show()
都不够。
虽然 inject()
确实有一个 env
参数,但这仅用于 评估 表达式 在 它之后已注射。没有 inj_env
from 可以注入参数,因为它们总是从调用上下文中获取。
此外,没有选择 return 注入的表达式 本身 ,因为 inject()
总是 return [= 的结果156=]正在评估 env
.
中的表达式
至于qq_show()
,它打印注入的表达式本身,但它不return它作为对象:return 值为 NULL
。与 inject()
一样,它也缺少用于注入参数的 inj_env
。
尝试次数
我在这里使用这种方法取得了一些成功:
unquo_1 <- function(expr, inj_env) {
inj_expr <- substitute(inject(quote(expr)))
eval_bare(inj_expr, inj_env)
}
这个想法是,当我们调用类似 unquo_1(!!a + 1, global_env())
的东西时,它会创建一个 inj_expr
的 inject(quote(!!a + 1))
。这将在 inj_env
中计算,这里包括对象 a
:符号 a.global
。因此 inject()
将取消对 !!a
的引用以得到 quote(a.global + 1)
,然后对其求值(也在 inj_env
中)。结果只是表达式 a.global + 1
.
正如我在 目标 中对行为的说明一样,这通常会按预期工作:
unquo_1(!!a + 1, global_env())
#> a.global + 1
unquo_1(!!a + !!b, global_env())
#> a.global + b.global
unquo_1(!!a + !!z, global_env())
#> Error in enexpr(expr) : object 'z' not found
但是,有一个微妙但关键的边缘情况,它破坏了整个目的:
unquo_1(!!a + 1, my_env)
#> Error in inject(quote(!!a + 1)) : could not find function "inject"
与a
不同,函数inject
是my_env
及其环境祖先中的一个未定义对象。如果它的定义不同,如 env_bind(my_env, inject = base::stop)
,那么它的行为仍然完全没有帮助。这同样适用于函数 quote
、`!`
等。
我找到的最佳解决方案是重新定义 inj_expr
以完全限定 rlang::inject()
和 base::quote()
:
unquo_1 <- function(expr, inj_env) {
inj_expr <- substitute(rlang::inject(base::quote(expr)))
eval_bare(inj_expr, inj_env)
}
这个“解决方案”本身只会产生另一个错误
Error in rlang::inject : could not find function "::"
因为 `::`
在 inj_env
中不可用。但是从 data_mask
ing 惯例中得到启发
# A common situation where you'll want a multiple-environment mask
# is when you include functions in your mask. In that case you'll
# put functions in the top environment and data in the bottom. This
# will prevent the data from overwriting the functions.
top <- new_environment(list(`+` = base::paste, c = base::paste))
可在 inj_env
中访问的简单调整 env_bind
(inj_env, "::" = `::`)
will make the function `::`()
。因此,这一调整有助于通过 pkg::fn
访问任何包 pkg
!
中的任何函数 fn
然而,这仍然会使 unquo_1()
暴露于 命名冲突 。如果有人想用一个名为 ::
的替代函数注入表达式 !!`::`
怎么办?
我确实希望 inj_env
(及其父级)的内容完全用户提供的内容。
建议
我尝试过 function factories, for the sake of associating an injector with a custom environment. The documentation for rlang::env_bind_lazy()
没有成功
# By default the expressions are evaluated in the current
# environment. For instance we can create a local binding and refer
# to it, even though the variable is bound in a different
# environment:
who <- "mickey"
env_bind_lazy(env, name = paste(who, "mouse"))
env$name
#> [1] "mickey mouse"
# You can specify another evaluation environment with `.eval_env`:
eval_env <- env(who = "minnie")
env_bind_lazy(env, name = paste(who, "mouse"), .eval_env = eval_env)
env$name
#> [1] "minnie mouse"
但我缺乏利用它的专业知识。
或者,检查 rlang::inject()
的源代码
function (expr, env = caller_env())
{
.External2(rlang_ext2_eval, enexpr(expr), env)
}
的重要性
function (arg)
{
.Call(rlang_enexpr, substitute(arg), parent.frame())
}
这反过来表明 DLL rlang:::rlang_enexpr
是必不可少的:
$name
[1] "rlang_enexpr"
$address
<pointer: 0x7ff630452a60>
attr(,"class")
[1] "RegisteredNativeSymbol"
$dll
DLL name: rlang
Filename:
/Library/Frameworks/R.framework/Versions/4.1/Resources/library/rlang/libs/rlang.so
Dynamic lookup: FALSE
$numParameters
[1] 2
attr(,"class")
[1] "CallRoutine" "NativeSymbolInfo"
这似乎起源于 here 作为 C:
中的源代码
r_obj* ffi_enexpr(r_obj* sym, r_obj* frame) {
return capture(sym, frame, NULL);
}
然而,我缺乏 C 语言的技能来跟踪这里的取消引用是如何实现的,更不用说为我自己的包重写 ffi_enexpr
。
您真的需要在环境中存储您想要的符号吗?似乎如果你只是存储 symbols/expressions 那么你可以更容易地在像 exprs
这样的容器中做到这一点,然后可以使用 with_bindings
函数来替换一些值。所以如果你有
a <- expr(a.global)
b <- expr(b.global)
my_env <- exprs(a = a.custom)
那你就可以了
with_bindings(expr(!!a + 1))
# a.global + 1
with_bindings(expr(!!a + 1), !!!my_env)
# a.custom + 1
with_bindings(expr(!!a + !!z), !!!my_env)
# Error in enexpr(expr) : object 'z' not found
一旦你有了正确的表达式,你就可以在任何你喜欢的地方计算它。
原创
a <- sym("a.global")
b <- sym("b.global")
# Note this must be an environment that inherits from
# base. `new_environment()` creates envs that inherit from the empty
# env by default, which means even `::` is not in scope.
# Here we use `env()` which inherits from the current env by default.
my_env <- env(a = sym("a.custom"))
expr2 <- function(expr, env = caller_env()) {
# Grab the defused expression using base R to avoid processing
# rlang injection operators
expr <- substitute(expr)
# Inject the expression within `expr()` so it can process the
# operators within `env`. Qualify with `::` because `env`
# potentially doesn't have `expr()` in scope.
inject(rlang::expr(!!expr), env)
}
expr2(!!a + !!b, my_env)
#> a.custom + b.global
expr2(!!a + !!b)
#> a.global + b.global
更新
您可以 in the call, as implemented here 在 GitHub:
expr2 <- function(expr, env = caller_env()) {
# Grab the defused expression using base R to avoid processing
# rlang injection operators
expr <- substitute(expr)
# Inject the expression within `expr()` so it can process the
# operators within `env`. Inline `expr` in case it isn't in scope
inject((!!rlang::expr)(!!expr), env)
}
更新
事实证明rlang::expr_interp()
这个函数基本上达到了我的目的
unquo_2 <- function(expr, inj_env = rlang::caller_env(), eval_env = NULL) {
# Capture verbatim the argument passed to 'expr'...
expr_quo <- rrlang::enquo0(expr)
# ...and extract it literally as an expression.
expr_lit <- rlang::quo_get_expr(expr_quo)
# Unquote that expression in the context of the injection environment.
expr_inj <- rlang::expr_interp(expr_lit, inj_env)
# As desired, return either the unquoted expression itself...
if(rlang::is_null(eval_env)) {
expr_inj
}
# ...or the result of evaluating it in the evaluation environment.
else {
rlang::eval_bare(expr_inj, eval_env)
}
}
不幸的是,expr_interp()
是 deprecated in favor of inject()
,它既不适合单独的注入环境,也不适合在评估之前 return 表达式。
目标
我正在开发一个包,我需要一个行为类似于 rlang::inject()
or rlang::qq_show()
的函数。此函数的形式应为
unquo <- function(expr, inj_env) {
# ...
}
接受表达式 expr
并注入来自 inj_env
的参数。然后它 return 是注入的表达式本身, 没有 评估它。
例如:
library(rlang)
# Arguments to inject from global environment.
a <- sym("a.global")
b <- sym("b.global")
# Arguments to inject from custom environment, which has no parent.
my_env <- new_environment(list(a = sym("a.custom")))
# Injecting from global environment.
unquo(!!a + !!b, global_env())
#> a.global + b.global
# Injecting from custom environment...
unquo(!!a + 1, my_env)
#> a.custom + 1
# ...where 'b' neither exists nor is inherited.
unquo(!!a + !!b, my_env)
#> Error in enexpr(expr) : object 'b' not found
障碍
不幸的是,inject()
和 qq_show()
都不够。
虽然 inject()
确实有一个 env
参数,但这仅用于 评估 表达式 在 它之后已注射。没有 inj_env
from 可以注入参数,因为它们总是从调用上下文中获取。
此外,没有选择 return 注入的表达式 本身 ,因为 inject()
总是 return [= 的结果156=]正在评估 env
.
至于qq_show()
,它打印注入的表达式本身,但它不return它作为对象:return 值为 NULL
。与 inject()
一样,它也缺少用于注入参数的 inj_env
。
尝试次数
我在这里使用这种方法取得了一些成功:
unquo_1 <- function(expr, inj_env) {
inj_expr <- substitute(inject(quote(expr)))
eval_bare(inj_expr, inj_env)
}
这个想法是,当我们调用类似 unquo_1(!!a + 1, global_env())
的东西时,它会创建一个 inj_expr
的 inject(quote(!!a + 1))
。这将在 inj_env
中计算,这里包括对象 a
:符号 a.global
。因此 inject()
将取消对 !!a
的引用以得到 quote(a.global + 1)
,然后对其求值(也在 inj_env
中)。结果只是表达式 a.global + 1
.
正如我在 目标 中对行为的说明一样,这通常会按预期工作:
unquo_1(!!a + 1, global_env())
#> a.global + 1
unquo_1(!!a + !!b, global_env())
#> a.global + b.global
unquo_1(!!a + !!z, global_env())
#> Error in enexpr(expr) : object 'z' not found
但是,有一个微妙但关键的边缘情况,它破坏了整个目的:
unquo_1(!!a + 1, my_env)
#> Error in inject(quote(!!a + 1)) : could not find function "inject"
与a
不同,函数inject
是my_env
及其环境祖先中的一个未定义对象。如果它的定义不同,如 env_bind(my_env, inject = base::stop)
,那么它的行为仍然完全没有帮助。这同样适用于函数 quote
、`!`
等。
我找到的最佳解决方案是重新定义 inj_expr
以完全限定 rlang::inject()
和 base::quote()
:
unquo_1 <- function(expr, inj_env) {
inj_expr <- substitute(rlang::inject(base::quote(expr)))
eval_bare(inj_expr, inj_env)
}
这个“解决方案”本身只会产生另一个错误
Error in rlang::inject : could not find function "::"
因为 `::`
在 inj_env
中不可用。但是从 data_mask
ing 惯例中得到启发
# A common situation where you'll want a multiple-environment mask # is when you include functions in your mask. In that case you'll # put functions in the top environment and data in the bottom. This # will prevent the data from overwriting the functions. top <- new_environment(list(`+` = base::paste, c = base::paste))
可在 inj_env
中访问的简单调整 env_bind
(inj_env, "::" = `::`)
will make the function `::`()
。因此,这一调整有助于通过 pkg::fn
访问任何包 pkg
!
fn
然而,这仍然会使 unquo_1()
暴露于 命名冲突 。如果有人想用一个名为 ::
的替代函数注入表达式 !!`::`
怎么办?
我确实希望 inj_env
(及其父级)的内容完全用户提供的内容。
建议
我尝试过 function factories, for the sake of associating an injector with a custom environment. The documentation for rlang::env_bind_lazy()
没有成功
# By default the expressions are evaluated in the current # environment. For instance we can create a local binding and refer # to it, even though the variable is bound in a different # environment: who <- "mickey" env_bind_lazy(env, name = paste(who, "mouse")) env$name #> [1] "mickey mouse" # You can specify another evaluation environment with `.eval_env`: eval_env <- env(who = "minnie") env_bind_lazy(env, name = paste(who, "mouse"), .eval_env = eval_env) env$name #> [1] "minnie mouse"
但我缺乏利用它的专业知识。
或者,检查 rlang::inject()
function (expr, env = caller_env())
{
.External2(rlang_ext2_eval, enexpr(expr), env)
}
的重要性
function (arg)
{
.Call(rlang_enexpr, substitute(arg), parent.frame())
}
这反过来表明 DLL rlang:::rlang_enexpr
是必不可少的:
$name
[1] "rlang_enexpr"
$address
<pointer: 0x7ff630452a60>
attr(,"class")
[1] "RegisteredNativeSymbol"
$dll
DLL name: rlang
Filename:
/Library/Frameworks/R.framework/Versions/4.1/Resources/library/rlang/libs/rlang.so
Dynamic lookup: FALSE
$numParameters
[1] 2
attr(,"class")
[1] "CallRoutine" "NativeSymbolInfo"
这似乎起源于 here 作为 C:
中的源代码r_obj* ffi_enexpr(r_obj* sym, r_obj* frame) {
return capture(sym, frame, NULL);
}
然而,我缺乏 C 语言的技能来跟踪这里的取消引用是如何实现的,更不用说为我自己的包重写 ffi_enexpr
。
您真的需要在环境中存储您想要的符号吗?似乎如果你只是存储 symbols/expressions 那么你可以更容易地在像 exprs
这样的容器中做到这一点,然后可以使用 with_bindings
函数来替换一些值。所以如果你有
a <- expr(a.global)
b <- expr(b.global)
my_env <- exprs(a = a.custom)
那你就可以了
with_bindings(expr(!!a + 1))
# a.global + 1
with_bindings(expr(!!a + 1), !!!my_env)
# a.custom + 1
with_bindings(expr(!!a + !!z), !!!my_env)
# Error in enexpr(expr) : object 'z' not found
一旦你有了正确的表达式,你就可以在任何你喜欢的地方计算它。
原创
a <- sym("a.global")
b <- sym("b.global")
# Note this must be an environment that inherits from
# base. `new_environment()` creates envs that inherit from the empty
# env by default, which means even `::` is not in scope.
# Here we use `env()` which inherits from the current env by default.
my_env <- env(a = sym("a.custom"))
expr2 <- function(expr, env = caller_env()) {
# Grab the defused expression using base R to avoid processing
# rlang injection operators
expr <- substitute(expr)
# Inject the expression within `expr()` so it can process the
# operators within `env`. Qualify with `::` because `env`
# potentially doesn't have `expr()` in scope.
inject(rlang::expr(!!expr), env)
}
expr2(!!a + !!b, my_env)
#> a.custom + b.global
expr2(!!a + !!b)
#> a.global + b.global
更新
您可以
expr2 <- function(expr, env = caller_env()) {
# Grab the defused expression using base R to avoid processing
# rlang injection operators
expr <- substitute(expr)
# Inject the expression within `expr()` so it can process the
# operators within `env`. Inline `expr` in case it isn't in scope
inject((!!rlang::expr)(!!expr), env)
}