在 rlang::expr 中取消引用循环变量
Unquoting the loop variable in `rlang::expr`
我在使用运算符 !!
迭代构造涉及循环变量的表达式时遇到意外行为。
从循环变量本身创建一个表达式,例如,
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::expr( !!i )
str(X)
# List of 3
# $ : int 1
# $ : int 2
# $ : int 3
按预期工作。但是,试图构造任何涉及循环变量的复杂表达式,例如
Y <- list()
for( i in 1:3 )
Y[[i]] <- rlang::expr( 1 + (!!i) )
str(Y)
# List of 3
# $ : language 1 + 3L
# $ : language 1 + 3L
# $ : language 1 + 3L
似乎生成了包含循环变量最终值的表达式。这几乎就像表达式在循环期间被捕获为 quote( 1 + (!!i) )
,但是通过 !!
取消引用仅在整个循环执行后发生,而不是在每次迭代时发生。
有人可以解释一下这种行为吗?这是一个错误吗?预期目标是通过循环捕获未评估的表达式 1+1
、1+2
和 1+3
。因此 Y
的预期输出将是
# List of 3
# $ : language 1 + 1L
# $ : language 1 + 2L
# $ : language 1 + 3L
旁注:!!i
周围的额外括号是为了解决 .
使用 rlang 0.2.1
.
我不是 100% 确定我理解正确。您是否可能在 rlang::eval_tidy
之后计算表达式?
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::eval_tidy(rlang::expr(!!i + 1))
#[[1]]
#[1] 2
#
#[[2]]
#[1] 3
#
#[[3]]
#[1] 4
似乎 for
创造了某种环境/框架,在这个环境/框架中评估发生在最后(对于最后一个 i
)。一种可能的处理方法是避免 for
并使用 purrr::
或 lapply
.
Y <- purrr::map(1:3, ~ rlang::expr( 1 + (!! .) ))
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
Y <- lapply(1:3, function(i) rlang::expr( 1 + (!! i) ))
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
while
在控制台中工作,但在与 reprex
一起使用时失败(未显示)。
Y <- list()
i <- 1L
while (i <= 3) {
Y[[i]] <- rlang::expr( 1 + (!!i) )
i <- i + 1L
}
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
不完全确定是否是这种情况,但我认为这与惰性评估有关。主要思想是 R 在不使用时不计算表达式。在您的示例中, rlang::expr( !!i )
是 "self-evaluating" 因为表示常量的表达式是常量本身。这会导致在 for 循环的每次迭代中计算以下内容:
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::expr( !!i )
注意X
的元素不再是表达式而是整数:
> str(X)
List of 3
$ : int 1
$ : int 2
$ : int 3
identical(rlang::expr(1),1)
# [1] TRUE
然而,你的第二个例子有 rlang::expr( 1 + (!!i) )
,它在 for 循环的每次迭代中仍然是一个表达式,没有评估。惰性计算导致 R 仅在循环结束时计算 i
,它采用 i
的最后一个值。解决这个问题的方法是 force
评估 i
:
Y <- list()
for( i in 1:3 ){
force(i)
Y[[i]] <- rlang::expr( 1 + (!!i) )
}
> str(Y)
List of 3
$ : language 1 + 1L
$ : language 1 + 2L
$ : language 1 + 3L
请注意,懒惰求值过去也会影响 lapply
等函数,如本问题所述:Explain a lazy evaluation quirk,但此问题已在 R 3.2.0 中得到修复。 lapply
等高阶函数现在将参数强制传递给内部函数。请参阅@jhin 在同一问题中的回答。这就是为什么@Mike Badescu 的 lapply
解决方案现在有效。
我在使用运算符 !!
迭代构造涉及循环变量的表达式时遇到意外行为。
从循环变量本身创建一个表达式,例如,
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::expr( !!i )
str(X)
# List of 3
# $ : int 1
# $ : int 2
# $ : int 3
按预期工作。但是,试图构造任何涉及循环变量的复杂表达式,例如
Y <- list()
for( i in 1:3 )
Y[[i]] <- rlang::expr( 1 + (!!i) )
str(Y)
# List of 3
# $ : language 1 + 3L
# $ : language 1 + 3L
# $ : language 1 + 3L
似乎生成了包含循环变量最终值的表达式。这几乎就像表达式在循环期间被捕获为 quote( 1 + (!!i) )
,但是通过 !!
取消引用仅在整个循环执行后发生,而不是在每次迭代时发生。
有人可以解释一下这种行为吗?这是一个错误吗?预期目标是通过循环捕获未评估的表达式 1+1
、1+2
和 1+3
。因此 Y
的预期输出将是
# List of 3
# $ : language 1 + 1L
# $ : language 1 + 2L
# $ : language 1 + 3L
旁注:!!i
周围的额外括号是为了解决
使用 rlang 0.2.1
.
我不是 100% 确定我理解正确。您是否可能在 rlang::eval_tidy
之后计算表达式?
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::eval_tidy(rlang::expr(!!i + 1))
#[[1]]
#[1] 2
#
#[[2]]
#[1] 3
#
#[[3]]
#[1] 4
似乎 for
创造了某种环境/框架,在这个环境/框架中评估发生在最后(对于最后一个 i
)。一种可能的处理方法是避免 for
并使用 purrr::
或 lapply
.
Y <- purrr::map(1:3, ~ rlang::expr( 1 + (!! .) ))
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
Y <- lapply(1:3, function(i) rlang::expr( 1 + (!! i) ))
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
while
在控制台中工作,但在与 reprex
一起使用时失败(未显示)。
Y <- list()
i <- 1L
while (i <= 3) {
Y[[i]] <- rlang::expr( 1 + (!!i) )
i <- i + 1L
}
str(Y)
#> List of 3
#> $ : language 1 + 1L
#> $ : language 1 + 2L
#> $ : language 1 + 3L
不完全确定是否是这种情况,但我认为这与惰性评估有关。主要思想是 R 在不使用时不计算表达式。在您的示例中, rlang::expr( !!i )
是 "self-evaluating" 因为表示常量的表达式是常量本身。这会导致在 for 循环的每次迭代中计算以下内容:
X <- list()
for( i in 1:3 )
X[[i]] <- rlang::expr( !!i )
注意X
的元素不再是表达式而是整数:
> str(X)
List of 3
$ : int 1
$ : int 2
$ : int 3
identical(rlang::expr(1),1)
# [1] TRUE
然而,你的第二个例子有 rlang::expr( 1 + (!!i) )
,它在 for 循环的每次迭代中仍然是一个表达式,没有评估。惰性计算导致 R 仅在循环结束时计算 i
,它采用 i
的最后一个值。解决这个问题的方法是 force
评估 i
:
Y <- list()
for( i in 1:3 ){
force(i)
Y[[i]] <- rlang::expr( 1 + (!!i) )
}
> str(Y)
List of 3
$ : language 1 + 1L
$ : language 1 + 2L
$ : language 1 + 3L
请注意,懒惰求值过去也会影响 lapply
等函数,如本问题所述:Explain a lazy evaluation quirk,但此问题已在 R 3.2.0 中得到修复。 lapply
等高阶函数现在将参数强制传递给内部函数。请参阅@jhin 在同一问题中的回答。这就是为什么@Mike Badescu 的 lapply
解决方案现在有效。