Как обеспечить согласованность атрибутов вызова функций в lapply и Map?

Я хочу прикрепить вызов функции как attributute. Хотя использование fn() напрямую работает нормально, когда я запускаю его внутри lapply(), он возвращает X[[i]] вместо фактического имени, переданного в параметр d=. Я ожидаю увидеть на выходе df1 и df2, а не X[[i]].

> fn <- \(d, x=x, y=999, ...) {
+   cl <- match.call()
+   fa <- formals(fn)
+   fa[length(fa)] <- NULL
+   ma <- setdiff(names(fa), names(cl))
+   cl[ma] <- fa[ma]
+   cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
+   # cl$d <- quote(d)
+   res <- d[[1]]  ## so `res` depends on `d`
+   attr(res, 'call') <- cl
+   return(res)
+ }
> 
> z. <- 777
> df1 <- lst$df1
> df2 <- lst$df2
> fn(df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = df1, x = 666, z = 777, y = 999)
> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
FUN(d = X[[i]], x = 666, z = 777, y = 999)

$df2
[1] 1 2 3
attr(,"call")
FUN(d = X[[i]], x = 666, z = 777, y = 999)

Это, очевидно, относится к предыдущему вопросу , поэтому я попробовал Map как предложено, однако, видимо, это не дает хорошего обобщения.

> Map(list(df1=df1, df2=df2), f=fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
(\(d, x=x, y=999, ...) {
  cl <- match.call()
  fa <- formals(fn)
  fa[length(fa)] <- NULL
  ma <- setdiff(names(fa), names(cl))
  cl[ma] <- fa[ma]
  cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
  # cl$d <- quote(d)
  res <- d[[1]]
  attr(res, 'call') <- cl
  return(res)
})(d = dots[[1L]][[2L]], x = 666, z = 777, y = 999)

$df2
[1] 1 2 3
attr(,"call")
(\(d, x=x, y=999, ...) {
  cl <- match.call()
  fa <- formals(fn)
  fa[length(fa)] <- NULL
  ma <- setdiff(names(fa), names(cl))
  cl[ma] <- fa[ma]
  cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
  # cl$d <- quote(d)
  res <- d[[1]]
  attr(res, 'call') <- cl
  return(res)
})(d = dots[[1L]][[2L]], x = 666, z = 777, y = 999)

Затем я попытался сделать cl$d <- quote(d), cl$d <- quote(substitute(d)), cl$d <- quote(eval(parse(text=d))), cl$d <- quote(get(d)), как указано выше, но безрезультатно. Вот результат версии cl$d <- quote(d):

> fn(df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = d, x = 666, z = 777, y = 999)
> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
FUN(d = d, x = 666, z = 777, y = 999)

$df2
[1] 1 2 3
attr(,"call")
FUN(d = d, x = 666, z = 777, y = 999)

Ожидаемый результат

> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] TRUE
attr(,"call")
FUN(d = df1, x = 666, z = 777, y = 999)

$df2
[1] TRUE
attr(,"call")
FUN(d = df2, x = 666, z = 777, y = 999)

Внимание, я ищу решение, используя базу R.

Редактировать

После ответа @MrFlick я вижу, что мне нужно уточнить. На самом деле у меня большой список lst:

> lst <- list(df1=df1, df2=df2)
> lapply(lst, function(x) 
+   do.call("fn", list(as.name(x), x=666, z=z.))
+ )
Error in as.vector(x, mode = mode) : 
  'list' object cannot be coerced to type 'symbol'

Если lapply() передается именованный список, вы можете записать имена, используя sys.call(), например: вот . Хотя это некрасиво. Вам придется ввести некоторую логику, чтобы определить, вызывается ли функция из lapply() или напрямую. И я полагаю, что вы столкнетесь с трудностями в более сложных случаях, таких как вложенные вызовы lapply(). Если это вообще возможно, я бы попытался найти способ изменить функцию, чтобы она принимала желаемое имя в качестве аргумента, или создать функцию-оболочку с дополнительным аргументом, которая делает это.

SamR 22.04.2024 10:52

@SamR Интересно. Это близко, но дает мне d = list(df1 = df1, df2 = df2) на каждой итерации. Даже с Map при прохождении nm=c('df1', 'df2') и nmm внутри fn().

jay.sf 22.04.2024 11:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Однако вы не можете передавать имена переменных lapply вот так. Если вы хотите, чтобы ваша функция записывала вызов, и вас интересуют переменные имена, лучшим решением будет создание вызовов. Здесь мы можем сделать это с помощью do.call.

lapply(c("df1", "df2"), function(x) 
  do.call("fn", list(as.name(x), x=666, z=z.))
)
# [[1]]
# [1] TRUE
# attr(,"call")
# fn(d = df1, x = 666, z = 777)
# 
# [[2]]
# [1] TRUE
# attr(,"call")
# fn(d = df2, x = 666, z = 777)

Спасибо за ваш ответ, выглядит многообещающе, однако у меня возникнут проблемы, если list уже упакован, см. редактирование моего вопроса.

jay.sf 22.04.2024 18:16

@jay.sf. Потом lapply закончилось names(lst). Но тогда вы фактически не используете значения в списке. «Звонок», который вы создаете, на самом деле не сработает. fn(d = df2, x = 666, z = 777) отличается от fn(d = lst$df2, x = 666, z = 777), и только последний действительно будет использовать данные в списке, и нет никакой гарантии, что lst$df2 соответствует данным в df2. Вызов list() создает ленивую копию.

MrFlick 22.04.2024 18:47

Мы очень ценим do.call как особенный ключ, +1 за подсказку! Мне было бы любопытно, существует ли этот сбой только в R или на других языках.

jay.sf 23.04.2024 07:07
Ответ принят как подходящий

Я придумал это решение, которое использует элементы ответа @MrFlick, чтобы использовать do.call(), и из ответа передавать имена в Map() , цитируемые в ОП.

Нам просто нужно вставить одно предложение if в fn(). В вызове fn() заменяем d -- if присутствующий в многоточии -- на определенные параметры и attrибуты, или оставляем как есть.

> fn <- function(d, x=666, y=999, z, ...) {
+   cl <- match.call()
+   fa <- formals(fn)
+   fa[length(fa)] <- NULL
+   ma <- setdiff(names(fa), names(cl))
+   cl[ma] <- fa[ma]
+   cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
+   if (".name" %in% names(list(...))) {
+     .name <- sprintf('%s$%s', attr(d, 'lnm'), list(...)$.name)
+     cl$d <- str2lang(.name)
+     cl$.name <- NULL
+   } 
+   res <- d[[1]]
+   attr(res, 'call') <- cl
+   return(res)
+ }

При вызове lapply мы передаем names в параметре .name, который запускает предложение if в fn(), а также имя списка с помощью deparse(substitute(.)).

> lapply(names(lst), \(x) {
+   do.call("fn", 
+           list(
+             d=`attr<-`(
+               get(x, envir=as.environment(lst)), 'lnm', deparse(substitute(lst))
+               ), 
+             x=666, y=999, z=z., .name=x)
+   )})
$df1
[1] 1 2 3
attr(,"call")
fn(d = lst$df1, x = 666, y = 999, z = 777)

$df2
[1] 1 2 3
attr(,"call")
fn(d = lst$df2, x = 666, y = 999, z = 777)

Это также работает для одиночных запусков, как и предполагалось. Оба варианта позволяют копировать и выполнять вызов.

> fn(d=df3, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = df3, x = 666, z = 777, y = 999)
> 
> fn(d=lst$df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = lst$df1, x = 666, z = 777, y = 999)

Ограничения

Вызов Map не тривиален. Кроме того, необходимо изменить fn(), что не является проблемой для моего варианта использования, поскольку fn() принадлежит мне, но может привести к проблемам, если он взят из пакета. Проблемы могут возникнуть, если он используется в нескольких вложенных вызовах, которые не проверялись.


Данные:

> lst <- replicate(2, data.frame(matrix(1:12, 3, 4)), simplify=FALSE) |> 
+   setNames(c('df1', 'df2'))
> z. <- 777
> df3 <- lst$df1

Другие вопросы по теме