Как не попасть в ленивую пробную ловушку "

"R передает promises, а не значения. Обещание принудительно, когда оно сначала оценивается, а не когда оно передается.", см. этот ответ Г. Гротендика. Также см. этот вопрос, ссылаясь на книгу Хэдли.

В простых примерах, таких как

> funs <- lapply(1:10, function(i) function() print(i))
> funs[[1]]()
[1] 10
> funs[[2]]()
[1] 10

можно учитывать такое неинтуитивное поведение.

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

Рассмотрение такой проблемы приводит к трудным для отладки проблем, поскольку все вычисления выполняются бесперебойно - за исключением того, что результат неверен. Только явная проверка результатов выявляет проблему.

Что происходит сверху, так это то, что даже если бы я заметил такую ​​проблему, я никогда не был уверен, какие переменные мне нужны для force, а у меня нет.

Как я могу не попасть в эту ловушку? Существуют ли какие-либо шаблоны программирования, которые препятствуют тому или иному, по крайней мере, убедиться, что я заметил, что есть проблема?

Ответы

Ответ 1

Вы создаете функции с неявными параметрами, что не всегда является лучшей практикой. В вашем примере неявный параметр i. Другим способом переделать его было бы следующее:

library(functional)
myprint <- function(x) print(x)
funs <- lapply(1:10, function(i) Curry(myprint, i))
funs[[1]]()
# [1] 1
funs[[2]]()
# [1] 2

Здесь мы явно указываем параметры функции с помощью Curry. Обратите внимание, что мы могли бы иметь curried print напрямую, но здесь не для иллюстративных целей.

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

Другой вариант - захватить всю среду родительской функции, скопировать ее и сделать ее родительской env вашей новой функции:

funs2 <- lapply(
  1:10, function(i) {
    fun.res <- function() print(i)
    environment(fun.res) <- list2env(as.list(environment()))  # force parent env copy
    fun.res
  }
)
funs2[[1]]()
# [1] 1
funs2[[2]]()
# [1] 2

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

Ответ 2

Как отмечали другие, это может быть не лучший стиль программирования в R. Но один простой вариант - просто привыкнуть заставлять все. Если вы это сделаете, поймите, что вам не нужно на самом деле звонить force, просто оценка символа сделает это. Чтобы сделать его менее уродливым, вы могли бы начать практиковать такие функции:

myfun<-function(x,y,z){
   x;y;z;
   ## code
}

Ответ 3

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

Ответ 4

R имеет функцию, которая помогает защитить от ленивой оценки в ситуациях, подобных закрытию: forceAndCall().

В онлайн-справочной документации:

forceAndCall предназначен для определения функций более высокого порядка, таких как apply, чтобы вести себя более разумно, когда результат, возвращаемый применяемой функцией, является закрытием, которое захватывало его аргументы.