Использование get inside lapply внутри функции

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

В R мне нужна функция для возврата объекта именованного списка со всеми аргументами и значениями, введенными пользователем. Для этого я сделал этот код (игрушечный пример):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- frm
    for (i in 1:length(frm))
        parms[[i]] <- get(names(frm)[i])
    return(parms)
}

Итак, когда это спрашивают:

> foo(b=0)

$a
[1] 1

$b
[1] 0

$h
[1] "coconut"

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

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get)
    names(parms) <- names(frm)
    return(parms)
}

Проблема явно связана со средой, в которой get оценивает свой первый аргумент (строку символов, имя переменной). Это я знаю частично из сообщения об ошибке:

> foo(b=0)
Error in FUN(c("a", "b", "h")[[1L]], ...) : object 'a' not found

а также потому, что когда в окружении .GlobalEnv есть объекты с правильными именами, foo возвращает вместо них их значения:

> a <- 100
> b <- -1
> h <- 'wallnut'
> foo(b=0)
$a
[1] 100

$b
[1] -1

$h
[1] "wallnut"

Очевидно, что поскольку get по умолчанию оценивается в parent.frame(), он ищет объекты в среде .GlobalEnv, а не в текущей функции. Это странно, так как с первой версией функции такого не происходит.

Я перепробовал много вариантов, чтобы функция get оценивалась в нужной среде, но не смог сделать это правильно (я пробовал pos=-2,0,1,2 и envir=NULL в качестве вариантов).

Если кто-то знает немного больше меня об окружающей среде, особенно в этих «странных» случаях, я хотел бы знать, как это решить.

Спасибо за ваше время,

Хуан


person Juan    schedule 04.11.2011    source источник


Ответы (3)


Редактирование от 05 августа 2013 г.

Использование sapply() вместо lapply() значительно упрощает это:

foo4 <- function(a=1, b=5, h='coconut') {
    frm <- formals(sys.function())
    sapply(names(frm), get, envir=sys.frame(sys.parent(0)), simplify=FALSE)
}
foo4(b=0, h='mango')

Это, однако, без sapply() или lapply() может быть более элегантным решением:

foo5 <- function(a=1, b=5, h='coconut') {
    modifyList(formals(sys.function()), as.list(match.call())[-1])
}
foo5(b=0, h='mango')

Исходное сообщение (4 ноября 2011 г.)

Немного поразмыслив, это кажется лучшим решением.

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get, envir=sys.frame(sys.parent(0)))
    names(parms) <- names(frm)
    return(parms)
}
foo(b=0, h='mango')
# $a
# [1] 1

# $b
# [1] 0

# $h
# [1] "mango"

Здесь есть некоторые тонкие вещи, связанные с тем, как lapply определяет/оценивает вызовы, которые он создает. Подробности скрыты в вызове .Internal(lapply(X, FUN)), но для пробы сравните эти два вызова:

# With function matched by match.fun, search in sys.parent(0)
foo2 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           get, envir = sys.parent(0))
}

# With anonymous function, search in sys.parent(2)    
foo3 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           FUN = function(X) get(X, envir = sys.parent(2)))
}

foo4(a=0, h='mango')
foo5(a=0, h='mango')
person Josh O'Brien    schedule 04.11.2011
comment
...даже лучше, если бы у foo2 и foo3 были свои формалы, а не у foo :-) - person Tommy; 05.11.2011
comment
Джошу: будет ли то же самое, если я использую envr=sys.parent() + 1? - person Juan; 05.11.2011
comment
@Tommy -- было бы еще лучше, не так ли. Спасибо что подметил это. Я думаю, что frm <- formals() в целом даже лучше, но я оставлю его как есть, чтобы выделить единственное важное отличие. - person Josh O'Brien; 05.11.2011
comment
@ Хуан - ты прав. envir=sys.parent() + 1 работает (и envir=sys.parent(0) тоже). По-видимому, аргумент envir для get интерпретирует целые числа как относящиеся к пронумерованным кадрам в стеке вызовов (которые вы можете проверить с помощью sys.frames()). Таким образом, sys.frame(), которое я вставил в foo3, было совершенно ненужным. - person Josh O'Brien; 05.11.2011
comment
@Juan - Спасибо, кстати, за отличный оригинальный пост. Меня это тоже сводило с ума, несколько часов там! - person Josh O'Brien; 05.11.2011
comment
@ ДжошО'Брайен, спасибо! Это была просто проблема, с которой я столкнулся. И, кстати, стратегия, использованная в исходной функции foo (с циклом for), имеет свои проблемы: всякий раз, когда значение по умолчанию равно NULL, возникают проблемы... (например: foo <- function(a=NULL, b=2)). Это не происходит с версиями lapply. - person Juan; 07.11.2011
comment
Вместо sys.frame(sys.parent(0)) я думаю, что environment() более очевиден. На самом деле вы можете заменить всю эту строку на mget(names(frm), environment()) - person hadley; 07.08.2013
comment
Или вы можете заменить всю функцию на as.list(environment()) - person hadley; 07.08.2013

Просто преобразуйте текущую среду в список:

foo <- function(a=1, b=5, h='coconut') {
  as.list(environment())
}
foo(a = 0, h = 'mango')
person hadley    schedule 07.08.2013
comment
Да, круто, наверное, именно этого и добивался ОП. - person Josh O'Brien; 07.08.2013

Это адаптировано из приведенного выше решения @Josh O'Brien с использованием sapply для автоматического назначения правильных имен результирующему списку (сохраняет одну строку кода):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- sapply(names(frm), get, envir=sys.frame(sys.parent(-1)), simplify=FALSE)
    return(parms)
}
person Tony Breyal    schedule 04.11.2011