Как я могу написать функцию в Base R
для захвата точки-точки-точки (многоточия) и возврата списка неоцененных выражений?
В этом примере пользователь вводит выражения в фигурные скобки и получает нужные мне выходные данные:
fun1 <- function(...) {
as.list(substitute(list(...)))[-1L]
}
fun1({a = 2}, {a2 = a * 2})
> [[1]]
> {
> a = 2
> }
>
> [[2]]
> {
> a2 = a * 2
> }
Я бы хотел, чтобы пользователь выполнил тот же вызов, что и выше, без фигурных скобок и получил тот же вывод, что и в fun1()
:
fun2 <- function(...) {
# CODE HERE
}
fun2(a = 2, a2 = a * 2)
Добавляю к моему предыдущему комментарию: в зависимости от вашего варианта использования существуют разные подходы к решению этой проблемы. Один конкретный занимает «коробка», которая ожидает аргументы формы name = expr
.
Да, я согласен. Я пытаюсь удовлетворить очень специфические требования для подтверждения концепции, но постараюсь убедить директора, что есть способы получше. Спасибо за ссылку, посмотрю.
Не поймите меня неправильно: я не говорю, что то, что вы делаете, — плохая идея или вообще неосуществимая (хотя пользователь 2554330 правильно объяснил здесь синтаксическое ограничение R!). На самом деле, еще один способ обойти дилемму — использовать присваивание <-
вместо =
(хотя лично я бы этого не делал, так как налагаю строгий запрет на <-
в своих проектах).
Сделать это непросто, потому что =
в первом примере — это оператор присваивания, а =
во втором примере просто привязывает выражения к именам — это разные вещи.
Если вы действительно хотите преобразовать список аргументов в список присваиваний, это возможно, манипулируя выражениями. Я не рекомендую это делать, потому что это меняет смысл:
fun3 <- function(...) {
args <- as.list(substitute(list(...)))[-1L]
names <- names(args)
result <- args
names(result) <- NULL
for (i in seq_along(args))
if (nzchar(names[i]))
result[[i]] <- call(" = ", as.name(names[i]), args[[i]])
result
}
fun3(a = 2, a2 = a * 2, 123)
#> [[1]]
#> a = 2
#>
#> [[2]]
#> a2 = a * 2
#>
#> [[3]]
#> [1] 123
Created on 2024-07-13 with reprex v2.1.0
Спасибо за решение. Ваше предупреждение очень разумно.
Мне нравится предупреждение и ваш подход к этому, но неявное приведение вывода nchar()
к логическому значению для меня читается как Python. Я бы предпочел использовать здесь if (nzchar(names[i]))
.
@SamR: это хорошее предложение, я изменил его. Кстати, я думаю, что C оказал большее влияние на мое мышление, чем Python!
Обновление: добавление некоторых решений к исходным ответам, чтобы соответствовать ожидаемому результату.
Это немного хакерское решение, основанное на rlang
fun2 <- \(...){
random_things_from_users <- as.list(substitute(rlang::enquos(...)))
random_things_from_users <- random_things_from_users[-1L]
print(random_things_from_users)
}
fun2(a = 2, a2 = a * 2)
#> $a
#> [1] 2
#>
#> $a2
#> a * 2
fun3 = \(...){
user_args = as.list(substitute(rlang::enquos(...)))[-1L]
user_names <- names(user_args)
args_from_users <- purrr::map2(user_args,
user_names,
\(args, user_names) as.name(glue::glue('{user_names} = {deparse(args)}')))
names(args_from_users) <- NULL
args_from_users
}
fun3(a = 2, a2 = a * 2, b = 'hello world')
#> [[1]]
#> `a = 2`
#>
#> [[2]]
#> `a2 = a * 2`
#>
#> [[3]]
#> `b = "hello world"`
fun4 = \(...){
user_args = as.list(substitute(rlang::enquos(...)))[-1L]
user_names <- names(user_args)
args_from_users <- mapply(\(args, user_names) as.nameglue::glue('{user_names} = {deparse(args)}')),
args = user_args,
user_names = user_names)
names(args_from_users) <- NULL
args_from_users
}
Created on 2024-07-13 with reprex v2.1.0
Спасибо, но ваш fun2()
не дает такого же результата, как fun1()
. Yours возвращает именованный список, в котором часть a=
является именем, а не присваиванием внутри самого выражения. (Кроме того, я отредактировал свой первоначальный ответ, указав, что в этом приложении мне нужна только база R. Извините за это!)
Я думаю, что это fun2()
дает тот же результат, что и fun1()
, и в этом проблема. Чтобы получить показанный здесь результат, вам просто нужен исходный `as.list(substitute(list(...)))[-1L]`.
@Винсент понял, не волнуйся! Я обновил свой ответ, включив в него решение, соответствующее ожидаемому результату.
Извините, но мне кажется, что сейчас хуже. Теперь вы возвращаете вещи с такими именами, как a = 2
. Это не должно быть имя! это должно быть выражение. Честно говоря, я думаю, что оригинал Винсента as.list(substitute(list(...)))[-1L]
— лучшее решение на данный момент, лучше, чем ваше, лучше, чем мое.
Ах, я понимаю, что ты имеешь в виду! По сути, если бы мы захотели, мы могли бы взять его из списка, и он оценит его позже. Я допустил ошибку, сохранив элементы в списке как символы, соответствующие выводу в консоли, но не желаемому результату. Извините, что засорил этим все уведомления! Хотя работать было весело
Это похоже на проблему XY. Для чего именно вам это нужно? Требуете ли вы, чтобы функция принимала аргументы вида
name = expr
, или вы хотите принимать произвольное выражение и что вы хотите делать с результатом?