rlang :: sym в анонимных функциях

Недавно я заметил, что rlang::sym, похоже, не работает в анонимных функциях, и я не понимаю почему. Вот пример, он довольно неуклюжий и некрасивый, но я думаю, что он иллюстрирует суть

require(tidyverse)
data <- tibble(x1 = letters[1:3],
               x2 = letters[4:6],
               val = 1:3)

get_it <- function(a, b){
    data %>%
        mutate(y1 = !!rlang::sym(a)) %>%
        mutate(y2 = !!rlang::sym(b)) %>%
        select(y1, y2, val)
}
get_it("x1", "x2")

Это определяет некоторые игрушечные данные и (ужасную) функцию, которая по существу переименовывает столбцы на основе имен столбцов. Теперь я могу сделать то же самое для разных комбинаций a и b:

d <- tibble(x = c("x1", "x2"),
            y = c("x2", "x1"))
d %>% mutate(tmp = map2(x, y, get_it))

Однако, если я попытаюсь сделать то же самое с анонимной функцией, это не сработает:

d %>% mutate(tmp = map2(x, y, function(a, b){
data %>%
    mutate(y1 = !!rlang::sym(a)) %>%
    mutate(y2 = !!rlang::sym(b)) %>%
    select(y1, y2, val)
}))

Это не удается с object 'a' not found, хотя функции точно такие же, только здесь это анонимно. Кто-нибудь может объяснить почему?


person Jonas    schedule 17.08.2018    source источник
comment
Хм, настоящая загадка. Я думаю, что это должно быть связано со средой, в которой определена функция, но не смог понять разницу ...   -  person Calum You    schedule 18.08.2018
comment
Возможно, это не ошибка, но я бы сообщил об этом как о проблеме в Git.   -  person CPak    schedule 18.08.2018
comment
Если мы удалим rlang, который на самом деле здесь не нужен, тогда он будет работать: function(a, b) data %>% mutate(y1 = .[[a]], y2 = .[[b]]) %>% select(y1, y2, val) так что кажется, что анонимные функции работают, но не rlang в них.   -  person G. Grothendieck    schedule 18.08.2018
comment
Удаление кавычек не является вызовом функции: оно всегда действует в самой первой, самой внешней функции цитирования. Вот почему с анонимными функциями нужно быть осторожнее. Удаление кавычек происходит немедленно, в то время как анонимные функции обозначают область, которая создается позже, поэтому возникает проблема времени.   -  person Lionel Henry    schedule 18.08.2018
comment
Это одна из причин, по которой мы решили отказаться от UQ() и UQS(), которые слишком похожи на вызовы функций, несмотря на очень разную семантику.   -  person Lionel Henry    schedule 18.08.2018


Ответы (1)


Проблема не в анонимных функциях, а в приоритете оператора !!. На странице справки для !! указано, что

!! оператор отменяет кавычки своего аргумента. Он оценивается немедленно в окружающем контексте.

Это означает, что когда вы пишете сложное выражение NSE, такое как select внутри mutate, удаление кавычек будет происходить в среде выражения в целом. Как указывает @lionel, снятие кавычек имеет приоритет над другими вещами, такими как создание анонимных функциональных сред.

В вашем случае снятие кавычек !! выполняется по отношению к внешнему mutate(), который затем пытается найти столбец x1 внутри d, а не data. Есть два возможных решения:

1) Вытяните выражение, включающее !!, в отдельную функцию (как вы это сделали в своем вопросе):

res1 <- d %>% mutate(tmp = map2(x, y, get_it))

2) Замените !! на eval, чтобы отложить вычисление выражения:

res2 <- d %>% mutate(tmp = map2(x, y, function(a, b){
  data %>%
    mutate(y1 = eval(rlang::sym(a))) %>%
    mutate(y2 = eval(rlang::sym(b))) %>%
    select(y1, y2, val)
}))

identical(res1, res2)       #TRUE
person Artem Sokolov    schedule 18.08.2018
comment
В целом это хороший ответ, но проблема не во вложенном изменении. Это снятие кавычек внутри анонимной функции, как вы показали в примере !!i. - person Lionel Henry; 18.08.2018
comment
Хотя я думаю, что с вложенными мутациями вы можете создать аналогичную проблему с синхронизацией. - person Lionel Henry; 18.08.2018
comment
Спасибо за разъяснение, @lionel. Всегда приятно слышать от разработчиков. Причина, по которой я подумал, что это проблема вложенности, заключалась в том, что в моем последнем примере !! внутри анонимной функции и он отлично работает. Но ты прав; все сводится к приоритету операторов внутри выражений NSE, а не к вложенности. Я изменил формулировку ответа, чтобы быть более точным. (P.S. Большой поклонник вашего rlang пакета!) - person Artem Sokolov; 18.08.2018
comment
На основе моих связанных проблема, вы можете добавить к своему ответу, что использование eval или eval_tidy вместо !! решает проблему приоритета, которая возникает особенно в контексте вложенных mutates. - person TimTeaFan; 22.11.2019
comment
@TimTeaFan: Готово. Спасибо за предложение. - person Artem Sokolov; 22.11.2019