2009-05-07 27 views
31

Tengo un código de Haskell que funciona funciona correctamente en una lista infinita, pero no entiendo por qué puede hacerlo con éxito. (Modifiqué mi código original, que no manejaba listas infinitas, para incorporar algo de otro código en línea, y de repente veo que funciona pero no sé por qué).¿Por qué este código Haskell funciona correctamente con listas infinitas?

myAny :: (a -> Bool) -> [a] -> Bool 
myAny p list = foldr step False list 
    where 
     step item acc = p item || acc 

Mi comprensión de foldr es que hará un bucle a través de cada elemento de la lista (y tal vez que la comprensión es incompleta). Si es así, no debería importar cómo se redacte la función "paso" ... el código no debería ser capaz de manejar bucles infinitos.

Sin embargo, las siguientes obras:

*Main Data.List> myAny even [1..] 
True 

Por favor, me ayudan a entender: ¿por qué ??

Respuesta

46

Vamos a hacer un poco rastro en la cabeza de la forma en Haskell evaluará su expresión. Sustituyendo es igual a los iguales en cada línea, la expresión se evalúa con bastante rapidez a True:

myAny even [1..] 
foldr step False [1..] 
step 1 (foldr step False [2..]) 
even 1 || (foldr step False [2..]) 
False || (foldr step False [2..]) 
foldr step False [2..] 
step 2 (foldr step False [3..]) 
even 2 || (foldr step False [3..]) 
True || (foldr step false [3..]) 
True 

Esto funciona porque acc se pasa como un golpe seco sin evaluar (evaluación perezosa), sino también porque la función || es estricta en su primera argumento.

Así que esto termina:

True || and (repeat True) 

pero esto no significa:

and (repeat True) || True 

Mira la definición de || ver por qué este es el caso:

True || _ = True 
False || x = x 
+4

Además, puede verificar que el código no calcule más de 2 elementos con: 'myAny p list = foldr (\ ia -> trace (show i) (pi || a))' - que mostrará solo '1 2 True' – viraptor

+0

Guau, esta ha sido una respuesta MUY llamativa. Antes que nada, no comencé con la definición de foldr en frente de mí. Supuse que su código usaría funciones avanzadas que aún no conozco, así que solo lo consideraría como último recurso. Tu respuesta me impulsó a echar un vistazo, y eso aclara mucho. foldr en sí mismo está utilizando la recursión estructural "simple viejo". Me encanta la forma en que lo rompiste. Gracias. –

+0

BTW, es el || completamente estricto en su primera arg, o simplemente "da preferencia a" su primer arg? Por ejemplo, ¿qué pasaría si arg 2 ya hubiera sido evaluado, pero arg 1 todavía era solo un thunk? Y decir arg 2 era falso. ¿Hasckell cortocircuito en la dirección opuesta? Gracias. –

1

El punto clave aquí es que Haskell es un lenguaje no estricto. "No estricto" significa que permite funciones no estrictas, lo que a su vez significa que los parámetros de la función pueden no evaluarse por completo antes de que puedan ser utilizados. Obviamente, esto permite una evaluación perezosa, que es "una técnica para retrasar un cálculo hasta que se requiera el resultado".

inicio de this Wiki article

+0

Bien, sabía eso sobre la evaluación perezosa. Pero necesito ayuda para conectar los puntos de eso a por qué funciona el código anterior. Mientras tanto, veré el artículo de la wiki. Gracias. –

1

no sé Haskell, pero sospecho que, en su caso, funciona debido a la evaluación perezosa. Debido a que le permite trabajar con una lista infinitamente larga, cuando acceda a ella, calculará el resultado a medida que lo necesite.

Ver http://en.wikipedia.org/wiki/Lazy_evaluation

+0

Ese es un buen artículo, gracias por el enlace. –

18

Mi comprensión de foldr es que voluntad de bucle a través de cada elemento de la lista (y tal vez que la comprensión es incompleta).

foldr (a diferencia de foldl) no tiene que recorrer todos los elementos de la lista. Es instructivo observar cómo se define foldr.

foldr f z []  = z 
foldr f z (x:xs) = f x (foldr f z xs) 

Cuando se evalúa una llamada a foldr, obliga a la evaluación de una llamada a la función f. Pero tenga en cuenta cómo la llamada recursiva a foldr está incrustada en un argumento a la función f. Esa llamada recursiva no se evalúa si f no evalúa su segundo argumento.

+0

Buen punto. Inicialmente no sabía que la definición de foldr iba a ser comprensible a mi nivel de conocimiento de Haskell. Observo cómo señalas que la llamada recursiva se incrustó intencionalmente en un argumento de función, para convertirlo en un thunk. ¿Es algo que los desarrolladores de Haskell se encuentran haciendo mucho? ¿Hay casos en los que no necesita una función, pero la crea simplemente para pasar un argumento y saber que será un thunk? –

+2

Charlie, dado que es Haskell el que estamos usando, * nada * se evalúa a menos que algo lo obligue. –

Cuestiones relacionadas