programacion - Cómo no caer en la ''trampa de evaluación perezosa'' de R
funciones de la programacion funcional (4)
"R pasa promises , no valores. La promesa es forzada cuando se evalúa por primera vez, no cuando se pasa.", Vea esta respuesta por G. Grothendieck. También vea esta pregunta referente al libro de Hadley.
En ejemplos simples como
> funs <- lapply(1:10, function(i) function() print(i))
> funs[[1]]()
[1] 10
> funs[[2]]()
[1] 10
Es posible tener en cuenta este comportamiento no intuitivo.
Sin embargo, me encuentro frecuentemente cayendo en esta trampa durante el desarrollo diario. Sigo un estilo de programación bastante funcional, lo que significa que a menudo tengo una función A que devuelve una función B, donde B depende de alguna manera de los parámetros con los que se llamó a A. La dependencia no es tan fácil de ver como en el ejemplo anterior, ya que los cálculos son complejos y existen múltiples parámetros.
Pasar por alto este problema conduce a problemas difíciles de depurar, ya que todos los cálculos se realizan sin problemas, excepto que el resultado es incorrecto. Sólo una validación explícita de los resultados revela el problema.
Lo que viene encima es que incluso si he notado un problema así, nunca estoy realmente seguro de qué variables necesito force
y cuáles no.
¿Cómo puedo asegurarme de no caer en esta trampa? ¿Hay algún patrón de programación que evite esto o que al menos me asegure de que note que hay un problema?
Como otros lo señalaron, este podría no ser el mejor estilo de programación en R. Pero, una opción simple es simplemente adquirir el hábito de forzar todo. Si haces esto, date cuenta de que no necesitas llamar a la force
, solo evaluar el símbolo lo hará. Para hacerlo menos feo, podría ser una práctica comenzar funciones como esta:
myfun<-function(x,y,z){
x;y;z;
## code
}
Está creando funciones con parámetros implícitos, lo cual no es necesariamente la mejor práctica. En su ejemplo, el parámetro implícito es i
. Otra forma de volver a trabajar sería:
library(functional)
myprint <- function(x) print(x)
funs <- lapply(1:10, function(i) Curry(myprint, i))
funs[[1]]()
# [1] 1
funs[[2]]()
# [1] 2
Aquí, especificamos explícitamente los parámetros a la función usando Curry
. Tenga en cuenta que podríamos haber print
directamente, pero no aquí con fines ilustrativos.
Curry
crea una nueva versión de la función con parámetros preespecificados. Esto hace que la especificación del parámetro sea explícita y evita los posibles problemas que se están presentando porque Curry
obliga a las evaluaciones (hay una versión que no lo hace, pero que no ayudaría aquí).
Otra opción es capturar todo el entorno de la función principal, copiarlo y convertirlo en el entorno primario de su nueva función:
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
pero no lo recomiendo ya que potencialmente estarás copiando un montón de variables que quizás ni siquiera necesites. Peor aún, esto se complica mucho más si tiene capas anidadas de funciones que crean funciones. El único beneficio de este enfoque es que puede continuar con la especificación implícita de los parámetros, pero nuevamente, eso me parece una mala práctica.
Hay algunos trabajos en curso para mejorar las funciones de orden superior de R, como las funciones de aplicación, Reducir y el manejo de situaciones como éstas. Que esto se convierta en R 3.2.0 para ser lanzado en unas pocas semanas depende de qué tan disruptivos resulten los cambios. Debe quedar claro en una semana más o menos.
R tiene una función que ayuda a proteger contra la evaluación perezosa, en situaciones como la creación de cierre: forceAndCall()
.
De la documentación de ayuda en línea de R:
forceAndCall
está destinado a ayudar a definir funciones de orden superior, como aplicar para comportarse de manera más razonable cuando el resultado devuelto por la función aplicada es un cierre que capturó sus argumentos.