Sind erstklassige Fortsetzungen in modernen objektorientierten Programmiersprachen nützlich?

10

Fortsetzungen sind in funktionalen Programmiersprachen (z. B. der ContMonade in Haskell) äußerst nützlich, da sie eine einfache und regelmäßige Notation für Code im imperativen Stil ermöglichen. Sie sind auch in einigen älteren imperativen Sprachen nützlich, da sie zum Implementieren fehlender Sprachfunktionen (z. B. Ausnahmen, Coroutinen, grüne Threads) verwendet werden können. Aber für eine moderne objektorientierte Sprache mit Unterstützung gebaut für diese Funktionen, welche Argumente wäre es für auch Unterstützung Zugabe für erstklassige Fortsetzungen (ob der moderneren Stil begrenzt resetund shiftoder Schema-like call-with-current-continuation)?

Gibt es Argumente gegen das Hinzufügen anderer Unterstützung als Leistung und Komplexität der Implementierung?

Jules
quelle

Antworten:

15

Lassen wir Eric Lippert diese Frage beantworten:

Die offensichtliche Frage an dieser Stelle ist: Wenn CPS so großartig ist, warum verwenden wir es dann nicht die ganze Zeit? Warum haben die meisten professionellen Entwickler noch nie davon gehört oder denken diejenigen, die es haben, als etwas, was nur diese verrückten Scheme-Programmierer tun?

Erstens ist es für die meisten Menschen, die es gewohnt sind, über Unterprogramme, Schleifen, Try-Catch-Endlich usw. nachzudenken, einfach schwierig, darüber nachzudenken, dass Delegierte auf diese Weise für den Kontrollfluss verwendet werden. Ich überprüfe gerade meine Notizen zu CPS von CS442 und sehe, dass ich 1995 ein profQUOTE niedergeschrieben habe: „Mit Fortsetzungen muss man sich auf den Kopf stellen und sich von innen nach außen ziehen“. Professor Duggan (*) hat das absolut richtig gesagt. Wir erinnern uns an ein paar Tage zuvor, dass unser kleines Beispiel für die CPS-Transformation des C # -Ausdrucks M (B ()? C (): D ()) vier Lambdas umfasste. Nicht jeder kann Code lesen, der Funktionen höherer Ordnung verwendet. Es bringt eine große kognitive Belastung mit sich.

Darüber hinaus: Eines der schönen Dinge beim Einbetten bestimmter Kontrollflussanweisungen in eine Sprache ist, dass Ihr Code die Bedeutung des Kontrollflusses klar ausdrückt, während die Mechanismen ausgeblendet werden - die Aufrufstapel und Rücksprungadressen sowie Ausnahmehandlerlisten und geschützte Regionen und so weiter. Fortsetzungen machen die Mechanismen des Kontrollflusses in der Struktur des Codes explizit. All diese Betonung des Mechanismus kann die Bedeutung des Codes überwältigen .

Im nächsten Artikel erklärt er, wie Asynchronität und Fortsetzungen genau gleichwertig sind, und geht eine Demonstration durch, in der ein einfacher (aber blockierender) synchroner Netzwerkbetrieb ausgeführt und in asynchronem Stil neu geschrieben wird, wobei sichergestellt wird, dass alle verborgenen Elemente abgedeckt werden Fallstricke, die abgedeckt werden müssen, um es richtig zu machen. Es wird zu einem massiven Durcheinander von Code. Seine Zusammenfassung am Ende:

Heilige Güte, was für ein schreckliches Durcheinander haben wir gemacht. Wir haben zwei Zeilen mit klarem Code zu zwei Dutzend Zeilen der schrecklichsten Spaghetti erweitert, die Sie je gesehen haben. Und natürlich wird es immer noch nicht kompiliert, da die Labels nicht im Geltungsbereich liegen und wir einen eindeutigen Zuweisungsfehler haben. Wir müssten den Code noch weiter umschreiben, um diese Probleme zu beheben.

Erinnerst du dich, was ich über die Vor- und Nachteile von CPS gesagt habe?

  • PRO: Beliebig komplexe und interessante Kontrollabläufe können aus einfachen Teilen aufgebaut werden - prüfen.
  • CON: Die Überprüfung des Kontrollflusses über Fortsetzungen ist schwer zu lesen und schwer zu begründen - zu überprüfen.
  • CON: Der Code, der die Mechanismen des Kontrollflusses darstellt, überwältigt die Bedeutung der Codeüberprüfung vollständig.
  • CON: Die Umwandlung des normalen Codesteuerungsflusses in CPS ist die Art von Dingen, in denen Compiler gut sind, und fast niemand anderes ist - überprüfen.

Dies ist keine intellektuelle Übung. Echte Menschen schreiben am Ende immer Code, der moralisch dem oben genannten entspricht, wenn sie sich mit Asynchronität befassen. Und ironischerweise verbringen wir immer mehr Zeit damit, auf Dinge zu warten, die nicht prozessorgebunden sind , obwohl Prozessoren schneller und billiger geworden sind . In vielen Programmen wird ein Großteil der Zeit damit verbracht, den Prozessor auf Null zu setzen und darauf zu warten, dass Netzwerkpakete die Reise von England nach Japan und wieder zurück antreten oder dass sich Festplatten drehen oder was auch immer.

Und ich habe noch nicht einmal darüber gesprochen, was passiert, wenn Sie asynchrone Operationen weiter zusammenstellen möchten. Angenommen, Sie möchten ArchiveDocuments zu einer asynchronen Operation machen, die einen Delegaten benötigt. Jetzt muss der gesamte Code, der ihn aufruft, auch in CPS geschrieben werden. Der Makel breitet sich einfach aus.

Es ist wichtig, die asynchrone Logik richtig zu machen , sie wird in Zukunft nur noch wichtiger sein, und die Werkzeuge, die wir Ihnen gegeben haben, lassen Sie „auf dem Kopf stehen und sich auf den Kopf stellen“, wie Professor Duggan mit Bedacht sagte.

Der Continuation-Passing-Stil ist mächtig, ja, aber es muss einen besseren Weg geben, diese Kraft gut zu nutzen als den obigen Code.

Beide Artikel und die folgende Serie über eine neue C # -Sprachenfunktion, die all dieses Durcheinander in den Compiler überträgt und es Ihnen ermöglicht, Ihren Code als normalen Kontrollfluss mit einem speziellen Schlüsselwort zu schreiben, um bestimmte Teile als asynchron zu markieren, sind auch dann lesenswert, wenn Sie Ich bin kein C # -Entwickler. Ich bin es nicht, aber es war immer noch eine ziemlich aufschlussreiche Erfahrung, als ich zum ersten Mal darauf stieß.

Mason Wheeler
quelle
4
Es ist erwähnenswert, dass Fortsetzungen, wenn Ihre Sprache Syntaxerweiterungen wie Makros unterstützt, weitaus nützlicher werden, da Sie die Mechanismen zum Implementieren verschiedener Arten von interessanten Steuerungsabläufen in den Syntaxerweiterungen verbergen können. So funktioniert "Warten" im Grunde sowieso, es wird nur in der Sprache definiert, da C # nicht die Werkzeuge bietet, um diese Art von Syntaxerweiterungen selbst zu definieren.
Jack
Guter Artikel, obwohl ich feststellen werde, dass CPS und erstklassige Fortsetzungen nicht genau dasselbe sind (CPS ist das, was Sie in einer Sprache ohne Unterstützung für Fortsetzungen tun, wenn Sie feststellen, dass Sie sie benötigen). Es hört sich so an, als würde C # genau den gleichen Weg einschlagen, über den ich nachdenke (obwohl ich mich nicht entscheiden kann, ob ich die Reifentransformation in CPS automatisieren oder durchgehend durch Stack-Copy getrennte Fortsetzungen implementieren möchte).
Jules
@Jack - genau das überlege ich mir gerade: Die Sprache, die ich entwerfe, verfügt über eine Makrofunktion, die die CPS-Transformation automatisch unterstützen könnte. Vollständige native Fortsetzungen können jedoch zu einer besseren Leistung führen. Die Frage ist, wie oft sie in einer leistungskritischen Situation verwendet werden.
Jules