是否可以使用rlang构造赋值表达式?

时间:2017-07-12 20:34:52

标签: r metaprogramming rlang

我尝试使用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:我写了proof of concept来演示我想要完成的任务。它定义了assign_general函数,允许任意左侧,例如assign_general(a[[1]], 5)相当于a[[1]] <- 5。然而,我的实施似乎有点hackish,我不知道我可能错过了什么角落,我仍然不确定是否有更直接的方式来做,所以我&#39 ; m仍然有兴趣看看是否有人有更好的解决方案。

3 个答案:

答案 0 :(得分:1)

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)assign_general的rlang版本可以通过将assign_general替换为call和{{1}来获取(2b)中lang的rlang实现与substitute

enexpr

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

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

答案 1 :(得分:0)

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

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

上面你可以看到lhs和rhs都作为字符串传递,表达式被赋值给L值。

答案 2 :(得分: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