2

我正在尝试使用 rlang 包来构造一个执行赋值的表达式,给定一个右侧表达式(要分配的值)和一个左侧表达式(分配它的位置)。例如,假设我要构造和评估表达式a <- 5

> library(rlang)
> a <- "Not 5"
> lhs <- quo(a)
> rhs <- quo(5)
> eval_tidy(quo( (!!lhs) <- (!!rhs)) ) # Error
Error in (~a) <- (~5) : could not find function "(<-"
> eval_tidy(quo(`<-`(!!lhs, !!rhs))) # Error
Error in ~a <- ~5 : could not find function "~<-"
> eval_tidy(quo(`<-`(!!f_rhs(lhs), !!rhs))) # No error, but no effect
[1] 5
> stopifnot(a == 5)
Error: a == 5 is not TRUE
> print(a)
[1] "Not 5"

如您所见,上述构造和评估此分配的方法都没有达到预期的效果。有没有办法正确地做到这一点?

编辑:使用assign而不是<-不是一个好的解决方案,因为它只适用于变量,而不是对象的元素。例如,它不适用于:

> a <- list(ShouldBeFive="Not 5")
> lhs <- quo(a$ShouldBeFive)

编辑 2:我写了一个概念证明来展示我想要完成的事情。它定义了一个assign_general允许任意左侧的函数,例如assign_general(a[[1]], 5)等效于a[[1]] <- 5. 但是,我的实现似乎有点骇人听闻,我不知道我可能错过了哪些极端情况,而且我仍然不确定是否有更直接的方法来做到这一点,所以我仍然有兴趣看看是否有人有一个更好的解决方案。

4

3 回答 3

2

1) rlang::lang我们可以rlang::lang这样使用:

library(rlang)

# inputs
a <- "Not 5"
lhs <- quote(a)
rhs <- 5

L <- lang("<-", lhs, rhs)
eval(L)
a
## [1] 5

2)调用或不rlang使用call代替lang

# inputs
a <- "Not 5"
lhs <- quote(a)
rhs <- 5
cc <- call("<-", lhs, rhs)

eval(cc)
a
## [1] 5

2a)以上两种方法在lhs适当表达的情况下也有效。例如使用内置数据框BOD

# inputs
BOD2 <- BOD
lhs <- quote(BOD2$xyz)
rhs <- 5

cc <- call("<-", lhs, rhs)
eval(cc)
names(BOD2)
## [1] "Time"   "demand" "xyz"   

2b)assign_general

assign_general <- function(lhs, rhs, envir = parent.frame()) {
       cc <- call("<-", substitute(lhs), substitute(rhs))
       eval(cc, envir = envir)
}

# test
a <- 1:5
assign_general(a[3], 5)
a
## [1] 1 2 5 4 5

该声明的一些替代方案call是:

cc <- substitute(call("<-", lhs, rhs))

或者

cc <- substitute(lhs <- rhs)

2c)当然这就足够了:

assign_general2 <- `<-`

a <- 1:5
assign_general2(a[3], 5)
## [1] 1 2 5 4 5

3) rlang 版本的 assign_generalassign_general可以通过用 和 替换来获得 (2b)calllangsubstituterlang实现enexpr

library(rlang)
assign_general3 <- function(lhs, rhs, envir = parent.frame()) {
       L <- lang("<-", enexpr(lhs), enexpr(rhs))
       eval(L, envir = envir)
}

# test
a <- 1:5
assign_general3(a[3], 5)
a
## [1] 1 2 5 4 5

4)字符串另一种可能性是将参数分解为字符串:

assign_general4 <- function(lhs, rhs, envir = parent.frame()) {
   s <- paste(deparse(substitute(lhs)), "<-", deparse(substitute(rhs)))
   p <- parse(text = s)
   eval(p, envir = envir)
}

# test
a <- 1:5
assign_general4(a[3], 5)
a
## [1] 1 2 5 4 5
于 2017-07-30T14:18:43.193 回答
0

元编程的优点是使用表达式作为字符串,并且能够对 L 值执行赋值,该 L 值也可以声明为字符串。在某些情况下,assign 函数可以在元编程中重用。

rhs <- "1 > 0"
assign("lhs", eval(eval_tidy(parse(text=rhs))))
lhs
[1] TRUE

上面你可以看到 lhs 和 rhs 都是作为字符串传递的,并且一个表达式被分配给一个 L 值。

于 2017-07-13T02:55:30.223 回答
0

借助一点黑魔法和一些运气,我能够实现您所追求的目标:

library(rlang)

expr<-quote(x<-1) # just some sample assignment operator to modify
a <- list(ShouldBeFive="Not 5")
lhs <- quo(a[[1]])
rhs <- quo(5)

expr[[2]] <-UQE(eval(lhs))
expr[[3]] <-UQE(eval(rhs))
expr
>a[[1]] <- 5

eval(expr)
a$ShouldBeFive
>5

这是希望更清洁的替代方案,不依赖于rlang

b <- list(ShouldBeSix="Not 6")
lhs <- quote(b[[1]])
rhs <- quote(6)

eval(substitute(x <- value,list(x = lhs, value = eval(rhs))))
b$ShouldBeSix
于 2017-07-28T20:56:16.093 回答