Cont monad shift

8

Während ich versuchte, etwas Intuition für den ContT-Monade-Transformer aufzubauen, fand ich mich (vielleicht nicht überraschend) verwirrt. Das Problem liegt in der shiftT-Operation, die nichts Nützliches zu tun scheint.

Zuerst ein einfaches Beispiel, wie man es benutzen könnte

%Vor%

famr a könnte ein etwas komplexerer Ausdruck sein, solange er m r zurückgibt. Nun, ein Versuch, meine Intuition zu erklären, dass shiftT ist, fügt nichts hinzu:

%Vor%

Es stellte sich heraus, dass wir ContT direkt erstellen könnten.

Fragestunde: Gibt es eine Situation, in der shift / shiftT etwas über cont / ContT hinzufügt? Oder werden sie nur verwendet, um den Code lesbarer zu machen?

    
Taren 29.04.2017, 12:20
quelle

2 Antworten

4

Nach dem Suche Github von gurkenglas 's Rat, den ich diese sehr schöne Erklärung von shiftT und resetT mit Anwendungsbeispielen, Motivation und Semantik!

Diese Funktionen sind sehr einfach. Ihre Definition in transformers Bibliothek ist einfach:

%Vor%

Aber Philosophie und Bedeutung liegen weit hinter einem intuitiven Verständnis. Daher empfehle ich Ihnen, die Erklärung über den obigen Link zu lesen. Manchmal passiert es, dass Dinge, die einfach zu definieren sind, etwas Komplexes tun können.

Angepasste Dokumentation aus der Erklärung in oben verlinkten Pugs:

  

shiftT

     

shiftT ist wie callCC , außer dass Sie die Fortsetzung aktivieren   von shiftT zur Verfügung gestellt, wird es bis zum Ende des nächsten einschließenden resetT laufen,   Springe dann zurück zu dem Punkt, an dem du die Fortsetzung aktiviert hast.   Beachten Sie, dass die Kontrolle schließlich zu dem Punkt nach dem zurückkehrt   Subkontinuation ist aktiviert, Sie können es mehrmals in der aktivieren   gleicher Block. Dies ist anders als in den Fortsetzungen von callCC , die den aktuellen Wert verwerfen   Ausführungspfad bei Aktivierung.

     

Siehe resetT für ein Beispiel, wie diese Begrenzungsunterteilungen tatsächlich aussehen   arbeiten.

     

resetT

     

Erstellen Sie einen Gültigkeitsbereich, für den shiftT die letzten Unterteilungen garantiert   Beenden Sie das Ende von. Betrachten Sie dieses Beispiel:

%Vor%      

Dies wird:

     
  1. Führe alfa

  2. aus   
  3. Führe bravo

  4. aus   
  5. Führe charlie

  6. aus   
  7. Binden Sie x an 1 und führen Sie daher zulu 1

  8. aus   
  9. Fällt vom Ende von resetT und springt direkt nach esc 1

  10. zurück   
  11. Führe delta

  12. aus   
  13. Binden Sie x an 2 und führen Sie daher zulu 2

  14. aus   
  15. Fällt vom Ende von resetT und springt direkt nach esc 2

  16. zurück   
  17. Escape von resetT , was dazu führt, dass es 0 ergibt

  18.   

Im Gegensatz zu den Fortsetzungen von callCC werden diese Unterkontinuationen schließlich   zurück zum Punkt, nachdem sie aktiviert wurden, nachdem sie vom Ende des   nächste resetT .

    
Shersh 29.04.2017, 14:59
quelle
2

Sie haben Recht, dass Fortsetzungen begrenzt undefinierten oder unzureichend abgegrenzten Fortsetzungen mit ausgedrückt werden kann. Daher können die Definitionen von shiftT und resetT immer nur mit ContT beschrieben werden. Aber:

  • Getrennte Fortsetzungen sind weniger leistungsstark . Dies macht es einfacher für Menschen zu implementieren und auch zu argumentieren. (Siehe auch viele andere interessante Beiträge über Fortsetzungen von Oleg Kiselyov ).
  • Die Verwendung der bekannten Notation shift / reset erleichtert das Verständnis, insbesondere für diejenigen, die mit dem Konzept vertraut sind.

Im Wesentlichen ermöglichen Fortsetzungen, ein Programm umzudrehen: Der durch reset abgegrenzte Block wird innerhalb des inneren Teils des Programms gequetscht, wenn shift die übergebene Funktion aufruft. (Im Falle von undefinierten Fortsetzungen ist der gesamte Ausführungskontext drinnen gedrängt, was sie so komisch macht.)

Lassen Sie uns ein paar Beispiele machen:

%Vor%

Wenn wir reset ohne shift haben, ist es nur eine reine Berechnung, nichts Besonderes. Die obige Funktion gibt einfach 0 zurück.

Nun können wir beide verwenden:

%Vor%

Das wird interessanter. Der Code zwischen shift und reset wird tatsächlich in die Aufrufe von esc gedrückt, in diesem einfachen Beispiel ist es nur return $ 1 + r . Wenn wir esc aufrufen, wird die gesamte Berechnung durchgeführt und ihr Ergebnis wird das Ergebnis des Aufrufs esc . Wir machen das zweimal, also rufen wir im Wesentlichen alles zwischen shift und reset zweimal auf. Und das Ergebnis der gesamten Berechnung ist result $ x * y , das Ergebnis des Aufrufs shift .

So in einem Sinn, der shift Block wird der äußere Teil der Berechnung und der Block zwischen reset und shift wird der innere Teil der Berechnung.

So weit, so gut. Aber es wird noch entmutigender, wenn wir shift zweimal aufrufen, wie in diesem Codebeispiel:

%Vor%

Und hier ist, was es produziert (versteckt für diejenigen, die versuchen wollen, es als Übung herauszufinden):

  

[(1,"a"),(1,"b"),(1,"c"),(2,"a"),(2,"b"),(2,"c"),(3,"a"),(3,"b"),(3,"c")]

Nun passiert das Programm zweimal :

  1. Zuerst ist alles außerhalb des Blocks x <- shift ... an den Aufruf yieldx gebunden, einschließlich des nächsten shift . Und das Ergebnis der Berechnung ist das Ergebnis des Blocks x <- shift ... .
  2. Zweitens, wenn die zweite y <- shift ... in yieldx aufgerufen wird, ist der Rest der Berechnung wiederum an den Aufruf yieldy gebunden. Und das Ergebnis dieser inneren Berechnung ist das Ergebnis des Blocks y <- shift ... .

Also lassen wir in x <- shift den Rest der Berechnung für jedes der drei Argumente laufen, und während jedes von ihnen machen wir eine ähnliche Sache für jedes der anderen drei Argumente. Das Ergebnis ist dann das kartesische Produkt der beiden Listen, da wir im Wesentlichen zwei verschachtelte Schleifen durchgeführt haben.

Das gleiche gilt für shiftT und resetT , nur mit zusätzlichen Nebenwirkungen. Wenn wir zum Beispiel debuggen wollen, was tatsächlich passiert, können wir den obigen Code in den IO monad- und print-Debugging-Anweisungen ausführen:

%Vor%     
Petr Pudlák 30.04.2017 20:15
quelle