Wie handle ich mit einer unendlichen Liste von IO-Objekten in Haskell?

8

Ich schreibe ein Programm, das aus einer Liste von Dateien liest. Jede Datei enthält entweder eine Verknüpfung zur nächsten Datei oder markiert, dass es das Ende der Kette ist.

Da es für Haskell neu ist, schien es, als wäre die idiomatische Art, dies zu handhaben, eine faule Liste möglicher Dateien zu diesem Zweck, die ich habe.

%Vor%

So weit, so gut. Das einzige Problem ist, dass, da getFirstFile und getNextFile beide Dateien öffnen müssen, ihre Ergebnisse in der IO-Monade liegen müssen. Dies gibt die modifizierte Form von

%Vor%

Das Problem dabei ist, dass, da iterate eine unendliche Liste zurückgibt, die Sequenz zu einer Endlosschleife wird. Ich bin mir nicht sicher, wie ich von hier aus vorgehen soll. Gibt es eine faule Form der Sequenz, die nicht alle Listenelemente trifft? Sollte ich die Karte neu justieren und takeWhile innerhalb der IO-Monade für jedes Listenelement arbeiten? Oder muss ich den gesamten unendlichen Listenprozess löschen und eine rekursive Funktion schreiben, um die Liste manuell zu beenden?

    
user640078 11.10.2011, 22:53
quelle

4 Antworten

13

Ein Schritt in die richtige Richtung

Was mich verwirrt, ist getNextFile . Begib dich mit mir in eine vereinfachte Welt, in der es noch kein IO gibt. Der Typ ist Maybe DataFile -> Maybe DataFile . Meiner Meinung nach sollte dies einfach DataFile -> Maybe DataFile sein, und ich gehe davon aus, dass diese Anpassung möglich ist. Und das sieht wie ein guter Kandidat für unfoldr aus. Das erste, was ich tun werde, ist meine eigene vereinfachte Version von unfoldr, die weniger allgemein aber einfacher zu verwenden ist.

%Vor%

Jetzt stimmt der Typ f :: a -> Maybe a mit getNextFile :: DataFile -> Maybe DataFile

überein %Vor%

Schön, oder? unfoldr ist sehr ähnlich wie iterate , außer wenn es Nothing erreicht, beendet es die Liste.

Jetzt haben wir ein Problem. %Code%. Wie können wir das Gleiche mit IO tun, das dort hineingeworfen wird? Denken Sie nicht einmal an die Funktion, die nicht benannt werden soll. Wir brauchen einen aufgepeppten Unfold, um damit fertig zu werden. Glücklicherweise ist die Quelle für unvoldr verfügbar zu uns.

%Vor%

Was brauchen wir jetzt? Eine gesunde Dosis von IO . IO fast bringt uns den richtigen Typ, wird ihn diesmal aber nicht ganz schneiden.

Eine tatsächliche Lösung

%Vor%

Es ist eine ziemlich direkte Umwandlung; Ich frage mich, ob es einen Kombinator gibt, der das Gleiche erreichen könnte.

Fun fact: Wir können nun liftM2 unfoldr

definieren

Lassen Sie uns noch einmal ein vereinfachtes unfoldr f b = runIdentity $ unfoldrM (return . f) b definieren, wir müssen nur ein myUnfoldrM darin einstreuen:

%Vor%

Und jetzt sind wir fertig, genau wie vorher.

%Vor%

Übrigens habe ich all diese Elemente mit liftM und den Typ-Signaturen für data DataFile = NoClueWhatGoesHere und getFirstFile überprüft, wobei ihre Definitionen auf getNextFile gesetzt wurden.

[edit] hat undefined und myUnfoldr geändert, um sich mehr wie myUnfoldrM zu verhalten, einschließlich des Anfangswerts in der Ergebnisliste.

[edit] Zusätzliche Einblicke zu entfaltet:

Wenn es Ihnen schwer fällt, sich zu entfalten, ist die Collatz-Sequenz möglicherweise eines der einfachsten Beispiele.

%Vor%

Denken Sie daran, iterate ist eine vereinfachte Entfaltung für die Fälle, in denen "next seed" und "current output value" identisch sind, wie es bei collatz der Fall ist. Dieses Verhalten sollte bei der einfachen Definition von myUnfoldr in myUnfoldr und unfoldr leicht zu erkennen sein.

%Vor%

Mehr, meist nicht verwandte Gedanken

Der Rest hat absolut nichts mit der Frage zu tun, aber ich konnte einfach nicht darüber nachdenken. Wir können tuplefy x = (x,x) in myUnfoldr definieren:

%Vor%

Vertraut? Wir können dieses Muster sogar abstrahieren:

%Vor%

myUnfoldrM sollte funktionieren, um jede Funktion der Form

zu "versenken" (Gegenteil von "lift")

sinkM .

, da die Monad m => (a -> m b) -> a -> m c in diesen Funktionen mit der Monad m -Monad-Einschränkung von Identity vereinheitlicht werden können. Allerdings ich nicht alles sehen , dass sinkM tatsächlich nützlich wäre.

    
Dan Burton 12.10.2011, 05:17
quelle
11
%Vor%

Erträge:

%Vor%     
Thomas Eding 11.10.2011 23:55
quelle
8

Wie Sie bemerkt haben, können IO-Ergebnisse nicht träge sein, so dass Sie (einfach) keine unendliche Liste mit IO erstellen können. Es gibt jedoch einen Ausweg in unsafeInterleaveIO ; Mit diesem können Sie etwas tun wie:

%Vor%

Es ist jedoch wichtig, hier vorsichtig zu sein - Sie haben die Ergebnisse von ioList auf eine unvorhersehbare Zeit in der Zukunft verschoben. Es wird vielleicht überhaupt nie ausgeführt werden. Also sei sehr vorsichtig, wenn du Clever ™ bist.

Persönlich würde ich nur eine manuelle rekursive Funktion erstellen.

    
bdonlan 11.10.2011 23:00
quelle
4

Faulheit und I / O sind eine knifflige Kombination. Die Verwendung von unsafeInterleaveIO ist eine Möglichkeit, faule Listen in der IO-Monade zu erzeugen (und dies ist die Technik, die von den Standard getContents , readFile und Freunden verwendet wird). So praktisch dies jedoch ist, macht es reinen Code möglichen E / A-Fehlern zugänglich und macht die Freigabe von Ressourcen (wie Dateihandles) nicht deterministisch. Aus diesem Grund verwenden die meisten "ernsthaften" Haskell-Anwendungen (insbesondere diejenigen, die sich mit Effizienz befassen) heutzutage sogenannte Enumeratoren und Iteraten für das Streaming von I / O. Eine Bibliothek in Hackage, die dieses Konzept implementiert, ist enumerator .

Sie sind wahrscheinlich in der Lage, lazy I / O in Ihrer Anwendung zu verwenden, aber ich dachte, ich würde dies immer noch als Beispiel für eine andere Art der Annäherung an diese Art von Problemen geben. Sie können weitere ausführliche Tutorials über iteratees hier und hier .

Beispielsweise könnte Ihr Stream von DataFiles als Enumerator wie folgt implementiert werden:

%Vor%     
shang 12.10.2011 12:24
quelle