foldr
(avec une fonction de combinaison stricte) déconstruit une liste d'arguments jusqu'à sa fin, puis la recombine à l'aide de la fonction de combinaison fournie par l'utilisateur. f
:
foldr f z [a,b,c,...,y] = f a (f b (f c (.....(f y z).....)))
dans votre cas,
fibs2 = reverse $ foldr (\_ (x:y:xs) -> (x+y):x:y:xs) [1,0] [0..]
foldr
ici ne produit jamais aucun résultat. Mais ce n'est pas faute d'avoir essayé : il travaille très dur en récurant la liste infinie à la recherche de sa fin, car sa fonction de combinaison est la suivante strict (il correspond au reste de foldr
avec (x:y:xs)
avant de construire son propre résultat).
Foldr
avec une fonction de combinaison stricte exprime la récursion, et la récursion doit avoir son cas de base, pour s'arrêter. Ce que vous aviez en tête est le suivant :
fibs2 = reverse $ snd $ until (null.fst)
(\(_t:ts, x:y:xs) -> (ts, (x+y):x:y:xs)) ([0..],[1,0])
qui est manifestement non terminale. ts
exprime simplement les moments de passage du temps. Nous pouvons essayer de voir toute l'histoire de son exécution, en la réécrivant en tant que
fibs2 = reverse $ snd $ last $ iterate
(\(_t:ts, x:y:xs) -> (ts, (x+y):x:y:xs)) ([0..],[1,0])
Bien sûr, il n'y a pas de dernier élément dans la liste infinie. Mais nous pouvons au moins voir tous les résultats intermédiaires maintenant :
> mapM_ (print.reverse.snd) $ take 11 $ iterate
(\(_:ts, x:y:xs) -> (ts, (x+y):x:y:xs)) ([0..],[1,0])
[0,1]
[0,1,1]
[0,1,1,2]
[0,1,1,2,3]
[0,1,1,2,3,5]
[0,1,1,2,3,5,8]
[0,1,1,2,3,5,8,13]
[0,1,1,2,3,5,8,13,21]
[0,1,1,2,3,5,8,13,21,34]
[0,1,1,2,3,5,8,13,21,34,55]
[0,1,1,2,3,5,8,13,21,34,55,89]
Ainsi, au lieu d'attendre que la toute dernière liste soit construite (ce qui sera jamais ) et puis en l'inversant, pourquoi ne pas produire ses éléments au fur et à mesure ? Tout est déjà là, de toute façon. Et le dernier élément de chaque résultat intermédiaire, inversé - n'est-il pas simplement son premier élément ?
> take 11 $ map (head.snd) $ iterate
(\(_:ts, x:y:xs) -> (ts, (x+y):x:y:xs)) ([0..],[1,0])
[1,1,2,3,5,8,13,21,34,55,89]
Neat, huh. Alors, avons-nous vraiment besoin de notre compteur de temps explicite ? Devons-nous faire glisser toute la queue du résultat intermédiaire précédent, ou avons-nous seulement besoin de ses deux premiers éléments ?
fibs = map (head.tail) $ iterate (\[x,y] -> [x+y,x]) [1,0]
L'utilisation de tuples pour les listes de longueur constante sera un peu plus propre. Vous voyez donc que nous sommes arrivés ici à l'une des définitions canoniques,
fibs = g (1,0) where g (a,b) = b : g (a+b,a)
(voir aussi Quelle est la différence entre la récursion et la corecursion ? ).
votre "deuxième essai",
fibs4 = 0:[ b | (a, b) <- scanl (\(x,y) _ -> (y,x + y)) (0,1) [0..] ]
est en fait très proche de ce qui précède, comme vous pouvez le voir. scanl
sur la liste de comptage du temps est juste équivalent à iterate
. C'est donc équivalent à
fibs4a = [a | (a,b) <- iterate (\(a,b) -> (b, a+b)) (0,1)]
que nous avons vu dans la dérivation ci-dessus comme une map
variante, avec des tuples au lieu de listes.