Как не попасть в ленивую пробную ловушку "
"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, чтобы вести себя более разумно, когда результат, возвращаемый применяемой функцией, является закрытием, которое захватывало его аргументы.