Ich versuche, einen grundlegenden Netzwerkserver mit Pipes und den darauf aufbauenden Bibliotheken zu schreiben. Der beabsichtigte Fluss wäre:
bekomme bytestring von socket - & gt; Dekodieren mit Binär - & gt; Server Logik geht hier - & gt; Antwort an Socket senden
Was ich dachte, wäre etwas wie:
%Vor% pipes-binary hat eine decode
und eine decodeMany
, aber ich bin mir nicht sicher, ob ich den Unterschied verstehe, und ich weiß nicht, wie man decode
benutzt. Warum verwendet decodeMany
die Upstream-Pipe als Argument, anstatt sie mit >->
zu verketten? Und wie benutzt du decode
, wofür ist StateT
und wie soll meine Pipe Chain aussehen?
Das StateT (Producer a m r) m x
idiom kommt von pipes-parse
"Low-Level-Parser" . Dies bedeutet normalerweise, dass die Bibliothek draw
und unDraw
verwendet, um Werte von Producer
abzuziehen und sie zurückzugeben, wenn sie nicht verwendet werden. Es ist ein wesentlicher Bestandteil der Analyse, wo Fehler auftreten können. Außerdem muss die Schicht StateT
angeben, dass eine Leitung selektiv in statusbehafteter Weise entleert und neu befüllt wird.
Was bedeutet das für decode
und decodeMany
? Wenn wir uns einige vereinfachte Typen dieser Funktionen anschauen
Wir sehen zuerst, dass decode
ist drawing
off genug ByteString
Chunks von Producer ByteString
Stateful, um zu versuchen, ein b
zu analysieren. Da die Chunk-Grenze in ByteString
s möglicherweise nicht mit einer Parse-Grenze übereinstimmt, ist es wichtig, dies in StateT
zu tun, damit die verbleibenden Chunks unDraw
-ed zurück in Producer
werden können.
decodeMany
baut auf decode
auf und versucht wiederholt, decode
b
s von der Eingabe Producer zurückzugeben, die bei einem Fehler eine "Fortsetzung" Producer
von übrig gebliebenem ByteString
s zurückgibt.
Lange Rede, kurzer Sinn, da wir unDraw
übrig gebliebene ByteString
-Brocken brauchen, können wir diese Dinge zusammen zu einer Kette mit (>->)
zusammenfassen. Wenn Sie das tun wollen, können Sie etwas wie decodeMany
verwenden, um einen Producer zu transformieren und dann das Ergebnis zu verketten, aber Sie sollten die Fehlerfälle sorgfältig behandeln.
Ich möchte J. Abrahamsons Antwort ergänzen, indem ich Ihre andere Frage beantworte, warum der Decoder kein Pipe
ist.
Der Unterschied zwischen einem Pipe
und einem Typ wie:
... und Funktion zwischen Producer
s wie (ich nenne diese "Getter" s):
... ist, dass ein Pipe
verwendet werden kann, um Producer
s, Consumer
s und andere Pipe
s:
... während ein "Getter" nur Producer
s transformieren kann. Einige Dinge können nicht richtig modelliert werden mit Pipe
s und Reste sind eines dieser Dinge.
conduit
gibt vor, Reste mit Conduit
s zu modellieren (das conduit
analog von Pipe
s), aber es ist falsch. Ich habe ein einfaches Beispiel zusammengestellt, das zeigt warum. Zuerst implementieren Sie einfach eine peek
Funktion für conduit
:
Dies funktioniert wie erwartet für einfache Fälle wie folgt:
%Vor%Dies wird das erste Element der Quelle zweimal zurückgeben:
%Vor% ... aber wenn Sie eine Conduit
upstream von Sink
erstellen, gehen alle Reste, die die Senke zurückdrückt, unwiderruflich verloren:
Nun gibt der zweite peek
falsch 2
zurück:
Beachten Sie auch, dass pipes-parse
gerade eine neue Hauptversion veröffentlicht hat, die die API vereinfacht und ein umfangreiches Tutorial hinzufügt, das Sie lesen können hier .
Diese neue API propagiert Reste weiter stromaufwärts korrekt. Hier ist das analoge Beispiel für pipes
:
Auch wenn das erste peek
ebenfalls auf die ersten 10 Werte beschränkt ist, zieht es den ersten Wert korrekt zurück und stellt ihn dem zweiten peek
zur Verfügung:
Der Grund, warum pipes-parse
"in Producer
s denkt", liegt konzeptionell daran, dass ansonsten das Konzept der Reste nicht klar definiert ist. Wenn Sie nicht klar definieren, was Ihre Quelle ist, können Sie nicht deutlich artikulieren, wo die Restewerte liegen sollten. Deshalb eignen sich Pipe
s und Consumer
s nicht gut für Aufgaben, die Reste benötigen.
Tags und Links haskell haskell-pipes