Dons a fourni une très bonne réponse, mais il a laissé de côté ce qui est (pour moi) l'une des caractéristiques les plus convaincantes des iteratees: ils facilitent le raisonnement sur la gestion de l'espace car les anciennes données doivent être explicitement conservées. Considérer:
average :: [Float] -> Float
average xs = sum xs / length xs
Il s'agit d'une fuite d'espace bien connue, car la liste entière xs
doit être conservée en mémoire pour calculer à la fois sum
et length
. Il est possible de faire un consommateur efficace en créant un pli:
average2 :: [Float] -> Float
average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs
-- N.B. this will build up thunks as written, use a strict pair and foldl'
Mais c'est quelque peu gênant de devoir faire cela pour chaque processeur de flux. Il y a quelques généralisations ( Conal Elliott - Beautiful Fold Zipping ), mais elles ne semblent pas avoir compris. Cependant, les iteratees peuvent vous procurer un niveau d'expression similaire.
aveIter = uncurry (/) <$> I.zip I.sum I.length
Ce n'est pas aussi efficace qu'un repli car la liste est toujours itérée plusieurs fois, mais elle est collectée par blocs afin que les anciennes données puissent être efficacement récupérées. Pour casser cette propriété, il est nécessaire de conserver explicitement toute l'entrée, comme avec stream2list:
badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list
L'état des itérés en tant que modèle de programmation est un travail en cours, mais il est bien meilleur qu'il y a encore un an. Nous apprenons ce que combinateurs sont utiles (par exemple zip
, breakE
, enumWith
) et qui le sont moins, avec le résultat qui a construit en iteratees et combinateurs fournissent sans cesse plus expressivité.
Cela dit, Dons a raison de dire qu'il s'agit d'une technique avancée; Je ne les utiliserais certainement pas pour tous les problèmes d'E / S.