Это немного сложно из-за сочетания семантики, связанной с этой проблемой. pmap()
принимает список и передает каждый элемент в качестве своего собственного аргумента функции (это в некотором смысле эквивалентно !!!
в этом смысле). Таким образом, ваша функция цитирования должна заключать свои аргументы в кавычки и каким-то образом передавать список столбцов в pmap()
.
Наша функция цитирования может идти одним из двух способов. Либо заключите в кавычки (то есть задержите) создание списка, либо сразу создайте фактический список выражений в кавычках:
quoting_fn1 <- function(...) {
exprs <- enquos(...)
# For illustration purposes, return the quoted inputs instead of
# doing something with them. Normally you'd call `mutate()` here:
exprs
}
quoting_fn2 <- function(...) {
expr <- quo(list(!!!enquos(...)))
expr
}
Поскольку наш первый вариант ничего не делает, кроме как возвращает список цитируемых входных данных, он фактически эквивалентен quos()
:
quoting_fn1(a, b)
#> <list_of<quosure>>
#>
#> [[1]]
#> <quosure>
#> expr: ^a
#> env: global
#>
#> [[2]]
#> <quosure>
#> expr: ^b
#> env: global
Вторая версия возвращает выражение в кавычках, которое инструктирует R создать список с входными данными в кавычках:
quoting_fn2(a, b)
#> <quosure>
#> expr: ^list(^a, ^b)
#> env: 0x7fdb69d9bd20
Между ними есть тонкое, но важное различие. Первая версия создает фактический объект списка:
exprs <- quoting_fn1(a, b)
typeof(exprs)
#> [1] "list"
С другой стороны, вторая версия не возвращает список, она возвращает выражение для создания списка:
expr <- quoting_fn2(a, b)
typeof(expr)
#> [1] "language"
Давайте выясним, какая версия больше подходит для взаимодействия с pmap()
. Но сначала мы дадим имя функции pmapped, чтобы сделать код понятнее и проще для экспериментов:
myfunction <- function(..., word) {
args <- list(...)
# just to be clear this isn't what I actually want to do inside pmap
args[[1]] + args[[2]]
}
Понимание того, как работает аккуратный eval, отчасти сложно, потому что мы обычно не наблюдаем шаг отмены цитирования. Мы будем использовать rlang::qq_show()
, чтобы показать результат отмены кавычек expr
(отложенный список) и exprs
(фактический список) с !!
:
rlang::qq_show(
mutate(df, outcome = pmap_int(!!expr, myfunction))
)
#> mutate(df, outcome = pmap_int(^list(^a, ^b), myfunction))
rlang::qq_show(
mutate(df, outcome = pmap_int(!!exprs, myfunction))
)
#> mutate(df, outcome = pmap_int(<S3: quosures>, myfunction))
Когда мы цитируем отложенный список, mutate()
вызывает pmap_int()
с list(a, b)
, вычисленным во фрейме данных, и это именно то, что нам нужно:
mutate(df, outcome = pmap_int(!!expr, myfunction))
#> # A tibble: 3 x 3
#> a b outcome
#> <int> <int> <int>
#> 1 1 101 102
#> 2 2 102 104
#> 3 3 103 106
С другой стороны, если мы приведем в кавычки фактический список выражений в кавычках, мы получим ошибку:
mutate(df, outcome = pmap_int(!!exprs, myfunction))
#> Error in mutate_impl(.data, dots) :
#> Evaluation error: Element 1 is not a vector (language).
Это потому, что выражения в кавычках внутри списка не оцениваются во фрейме данных. На самом деле они не оцениваются вообще. pmap()
получает выражения в кавычках как есть, чего не понимает. Вспомните, что qq_show()
показало нам:
#> mutate(df, outcome = pmap_int(<S3: quosures>, myfunction))
Все, что находится внутри угловых скобок, передается как есть. Это признак того, что мы должны были как-то использовать !!!
вместо этого, чтобы встроить каждый элемент списка предложений в окружающее выражение. Давайте попробуем это:
rlang::qq_show(
mutate(df, outcome = pmap_int(!!!exprs, myfunction))
)
#> mutate(df, outcome = pmap_int(^a, ^b, myfunction))
Хм ... Не выглядит правильно. Мы должны передать список в pmap_int()
, и здесь он получает каждый вход в кавычки в качестве отдельного аргумента. Действительно, мы получаем ошибку типа:
mutate(df, outcome = pmap_int(!!!exprs, myfunction))
#> Error in mutate_impl(.data, dots) :
#> Evaluation error: `.x` is not a list (integer).
Это легко исправить, просто соедините вызов list()
:
rlang::qq_show(
mutate(df, outcome = pmap_int(list(!!!exprs), myfunction))
)
#> mutate(df, outcome = pmap_int(list(^a, ^b), myfunction))
И вуаля!
mutate(df, outcome = pmap_int(list(!!!exprs), myfunction))
#> # A tibble: 3 x 3
#> a b outcome
#> <int> <int> <int>
#> 1 1 101 102
#> 2 2 102 104
#> 3 3 103 106