Dons hat eine sehr gute Antwort geliefert, aber er hat ausgelassen, was (für mich) eines der überzeugendsten Merkmale von Iteraten ist: Sie erleichtern das Nachdenken über die Speicherverwaltung, da alte Daten explizit beibehalten werden müssen. Erwägen:
average :: [Float] -> Float
average xs = sum xs / length xs
Dies ist ein bekanntes Speicherleck, da die gesamte Liste xs
gespeichert werden muss, um sowohl sum
als auch zu berechnen length
. Es ist möglich, einen effizienten Verbraucher zu machen, indem man eine Falte schafft:
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'
Es ist jedoch etwas unpraktisch, dies für jeden Stream-Prozessor tun zu müssen. Es gibt einige Verallgemeinerungen ( Conal Elliott - Beautiful Fold Zipping ), aber sie scheinen sich nicht durchgesetzt zu haben. Mit Iteraten können Sie jedoch eine ähnliche Ausdrucksebene erzielen.
aveIter = uncurry (/) <$> I.zip I.sum I.length
Dies ist nicht so effizient wie eine Falte, da die Liste immer noch mehrmals wiederholt wird. Sie wird jedoch in Blöcken gesammelt, damit alte Daten effizient durch Müll gesammelt werden können. Um diese Eigenschaft zu brechen, muss die gesamte Eingabe explizit beibehalten werden, z. B. bei stream2list:
badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list
Der Status von Iteraten als Programmiermodell ist in Arbeit, jedoch viel besser als noch vor einem Jahr. Wir lernen , was combinators nützlich sind (zB zip
, breakE
,enumWith
) und die weniger so, mit dem Ergebnis , dass die eingebauten in iteratees und combinators bietet ständig mehr Expressivität.
Das heißt, Dons ist richtig, dass sie eine fortgeschrittene Technik sind; Ich würde sie sicherlich nicht für jedes E / A-Problem verwenden.