Странное поведение мапли: что я пропустил? - PullRequest
16 голосов
/ 09 декабря 2011

Следующий код не работает, как я ожидал:

a <- list(0, 1)
b <- list(0, 1)

# return a linear function with slope `a` and intercept `b`.
f <- function(a, b) function(x) a*x + b

# create a list of functions with different parameters.
fs <- mapply(f, a, b)

# test
fs[[1]](3)
# [1] 4  # expected zero!
fs[[2]](3)
# [1] 4

Может кто-нибудь сказать мне, почему?

NB. Я нашел обходной путь, поэтому я не ищу другого способа достижения желаемого результата. Но мне любопытно, почему этот конкретный подход не сработал.


Обновление:

Начиная с версии 3.2.0, теперь это работает как ожидалось:

a <- list(0, 1)
b <- list(0, 1)
f <- function(a, b) function(x) a*x + b
fs <- mapply(f, a, b)

# test
fs[[1]](3)
# [1] 0 
fs[[2]](3)
# [1] 4

Ответы [ 2 ]

9 голосов
/ 09 декабря 2011

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

В вашем коде вы просто заполняете функции тем же обещанием a и тем же обещанием b;тогда они все были преданы последней паре долин.Как уже показывал @Tommy, решение состоит в том, чтобы принудительно выполнить обязательство путем «использования» значения до того, как функция будет определена.

8 голосов
/ 09 декабря 2011

[Обновление] Мой первоначальный анализ был правильным, но выводы были неверными :) Давайте сделаем выводы после анализа.

Вот код, демонстрирующий эффекты:

x <- lapply(1:3, function(x) sys.frame(sys.nframe()))
x[[1]] # An environment
x[[2]] # Another environment
x[[3]] # Yet nother environment
x[[1]]$x  # 3!!! (should be 1)
x[[2]]$x  # 3!!  (should be 2)
x[[3]]$x  # 3 as expected

# Accessing the variable within the function will "fix" the weird behavior:
x <- lapply(1:3, function(x) {x; sys.frame(sys.nframe())})
x[[1]]$x  # 1
x[[2]]$x  # 2
x[[3]]$x  # 3

Так что обходной путь в вашем случае:

f <- function(a, b) { a;b; function(x) a*x + b }

Кстати, как отмечает @James, есть функция force, которая делает доступ к переменной более явным:

f <- function(a, b) { force(a);force(b); function(x) a*x + b }

Выводы

Ну, как отметили @mbq и @hadley, это связано с ленивой оценкой. Это проще показать с помощью простого цикла for:

fs <- list(); for(i in 1:2) fs[[i]] <- f(a[[i]], b[[i]])

Аргумент функции f x будет не получить значение из a[[i]] (то есть 0), но все выражение и среда, в которой существуют a и i. Когда вы получаете доступ к x, он оценивается и поэтому использует i во время оценки. Если цикл for перешел с момента вызова на f, вы получите «неправильный» результат ...

Изначально я сказал, что это из-за ошибки в *apply, которой это не так. ... но так как я ненавижу ошибаться, я могу указать, что * применение имеет ошибку (или, возможно, больше несоответствия) в следующих случаях:

lapply(11:12, function(x) sys.call())
#[[1]]
#FUN(11:12[[1L]], ...)
#
#[[2]]
#FUN(11:12[[2L]], ...)

lapply(11:12, function(x) function() x)[[1]]() # 12
lapply(11:12, function(x) function() x)[[2]]() # 12

Как вы видите выше, lapply код говорит , что вызывает функцию с 11:12[[1L]]. Если вы оцените это «позже», вы должны по-прежнему получить значение 11 - но на самом деле вы получите 12!

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

QED

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...