Можно ли построить выражение присваивания с помощью 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: я написал доказательство концепции, которое демонстрирует, что Я пытаюсь выполнить. Он определяет функцию assign_general, которая допускает произвольные левые части, например. assign_general(a[[1]], 5) эквивалентно a[[1]] <- 5. Однако моя реализация кажется какой-то хакерской, я не знаю, какие краеугольные случаи я мог пропустить, и я до сих пор не уверен, есть ли более прямой способ сделать это, поэтому мне все еще интересно узнать, есть ли у кого-нибудь лучшее решение.


person Ryan C. Thompson    schedule 12.07.2017    source источник
comment
Можете ли вы просто использовать assign?   -  person Dason    schedule 13.07.2017
comment
assign не будет работать с произвольными левыми сторонами. Что, если lhs <- quo(a[[1]])?   -  person Ryan C. Thompson    schedule 13.07.2017
comment
Могу я спросить, почему вы так привязаны к использованию rlang?   -  person Dason    schedule 13.07.2017
comment
rlang — это инструмент метапрограммирования, с которым я лучше всего знаком. Если у вас есть хорошее решение, использующее что-то другое, это тоже хорошо.   -  person Ryan C. Thompson    schedule 13.07.2017


Ответы (3)


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))

or

cc <- substitute(lhs <- rhs)

2c) Конечно, этого было бы достаточно:

assign_general2 <- `<-`

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

3) rlang версия assign_general Реализация rlang assign_general в (2b) может быть получена путем замены call на lang и substitute на 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
person G. Grothendieck    schedule 30.07.2017
comment
rlang::lang больше не рекомендуется, используйте вместо него call2() и new_call(). - person yusuzech; 31.07.2019

Преимущество метапрограммирования заключается в использовании выражений в виде строк и возможности выполнять присваивания L-значению, которое также может быть объявлено как строка. В некоторых случаях функцию assign можно повторно использовать в метапрограммировании.

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

Выше вы можете видеть, что и левая, и правая стороны передаются как строки, а выражение присваивается L-значению.

person K. Troy    schedule 13.07.2017
comment
Как упоминалось в комментариях, assign не является достаточно общим. В отличие от <-, он не может назначаться элементам в списке или другим частям объектов. Он может назначать только имена переменных в среде. - person Ryan C. Thompson; 13.07.2017

С небольшим количеством темной магии и некоторой удачей я смог добиться того, что вам нужно:

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
person dmi3kno    schedule 28.07.2017