Algunos de ustedes han consultado sobre este ejercicio ("el de completar"). Voy a hacer un poco de desarrollo por acá. Si queda alguna duda podemos seguir discutiendo en este mismo hilo.
Dadas las siguentes definiciones:
loop x = map (const x) (loop x)
a = map loop [1. .]
z = 0 : zipWith (curry snd) a [1..]
hay que evaluar ciertas expresiones. Pero antes que nada me voy a concentrar en la función loop, que es la que creo que generó algunas dificultades. Observen que en general la expresión (loop x), si es evaluada, diverge. Porque la secuencia de evaluación queda más o menos así (para un valor x dado):
loop x ->
map (const x) (loop x) ->
map (const x) (map (const x) (loop x)) ->
map (const x) (map (const x) (map (const x) (loop x))) .....
Lo que ocurre es que como map se define por pattern matching sobre la lista que es su segundo argumento, para empezar a aplicar la función (const x) tenemos que reducir la lista argumento hasta "ver" el constructor, algo que nunca ocurrirá...
Por otro lado, expresión (a) sí va a producir elementos si la evaluamos. Abusando un poco de la notación de listas por comprensión, tenemos que:
a ->
map loop [1..] ->
loop 1 : map loop [2..] ->
loop 1 : loop 2 : map loop [3..] -> ...
Noten que si forzamos la evaluación de cualquier elemento de la lista la computación sí va a ser divergente. También si forzamos una recorrida de la lista completa (aunque la razón es un poco distinta).
Con (z), como tanto la lista (a) como la lista ([1..]) generan elementos, no tengo problemas para caminar por la lista y generar elementos, luego del primer cero me quedo con los segundos, que son los elementos de la lista [1..], así que no hay problema ninguno, z es la lista de los enteros no negativos en orden.
Ahora, a las piñas:
- (a !! 2) diverge, porque reduce a (loop 2)
- (sum $ take 5 z) es 10, (la suma de 0,1,2,3, y 4)
- (length $ map head $ take 5 a) es 5. Puedo tomar 5 elementos de a (son listas divergentes). Aplicar head sigue siendo divergente, pero como me interesa el largo no necesito evaluar eso. Al final lo hago paso a paso para que quede claro (en general, más allá de la intuición que uno desarrolla y que lo puede llevar a resolver estos ejercicios "a ojo", evaluar las expresiones -en call by name- es una forma metódica de resolver estos ejercicios.)
- (take 5 $ loop 3) está pidiendole constructores a (loop 3), como vimos, eso diverge.
- (head $ tail $ foldr (:) (loop 0) [1,2]) el fold va a poder generar una lista con 1 y 2 como primeros elementos (el resto diverge), y eso es suficiente.
- (map (const 1) (take 5 a)) reduce a [1,1,1,1,1]. Acá como la función que mapeamos es lazy en su argumento, no importa que la lista a tenga elementos que divergen, porque los descarto. (sí es importante que se generen los constructores. Por ejemplo si en lugar de a ponemos (loop x), se diverge)
- (length $ takeWhile (==True) (loop False)) diverge, porque la expresión (loop False) no puede generar ningún constructor (cosa que le "pide" takewhile, porque a takeWhile se lo "pide" length...)
- (last (foldl (flip (:)) [] x)) diverge. Tenemos un foldl para una lista infinita..
Ahora, para terminar, veamos cómo podemos reducir (length $ map head $ take 2 a). (cambié el 5 por un 2 para que me quede menos largo, pero la idea es la misma).
(length $ map head $ take 2a) ->
length tiene la posta y necesita ver un constructor (porque se define por pattern matching), entonces le pasa la posta a map. map necesita ver un constructor (de la lista, porque se define por pattern matching sobre ella) y por eso le pasa la posta a take. take está definida por pettern matching en los dos argumentos, ya sabe que el 2 es no nulo, necesita ver un constructor de la lista a, por lo que le pasa la posta. Con esta idea avanzo unos pasitos:
length (map head (take 2 a)) ->
length (map head (take 2 (map loop [1..])) ->
length (map head (take 2 (loop 1 : map loop [2..])) ->
ahora take vió constructor, por lo que vuelve a tener la posta y ahora se reduce:
length (map head (loop 1 : take (2-1) (map loop [2..])))
ahora map vió constructor, por lo que vuelve a tener la posta y se reduce:
length (head (loop 1) : map head (take (2-1) (map loop [2..])))
ahora length vió constructor, por lo que vuelve a tener la posta y se reduce:
1 + length (map head (take (2-1) (map loop [2..])))
y así seguimos...