Ich habe gerade die Programmierung in Scala beendet und mich mit den Änderungen zwischen Scala 2.7 und 2.8 befasst. Das wichtigste scheint das Fortsetzungs-Plugin zu sein, aber ich verstehe nicht, wofür es nützlich ist oder wie es funktioniert. Ich habe gesehen, dass es für asynchrone E / A gut ist, aber ich konnte nicht herausfinden, warum. Einige der beliebtesten Ressourcen zu diesem Thema sind:
- Abgrenzte Fortsetzungen und Scala
- Gehe in Scala
- Ein Vorgeschmack auf 2,8: Fortsetzung
- Abgrenzte Fortsetzung erklärt (in Scala)
Und diese Frage zum Stapelüberlauf:
Leider versucht keine dieser Referenzen zu definieren, wofür Fortsetzungen sind oder was die Shift / Reset-Funktionen tun sollen, und ich habe keine Referenzen gefunden, die dies tun. Ich konnte nicht erraten, wie eines der Beispiele in den verlinkten Artikeln funktioniert (oder was sie tun). Eine Möglichkeit, mir zu helfen, könnte darin bestehen, eines dieser Beispiele Zeile für Zeile durchzugehen. Auch dieser einfache aus dem dritten Artikel:
reset {
...
shift { k: (Int=>Int) => // The continuation k will be the '_ + 1' below.
k(7)
} + 1
}
// Result: 8
Warum ist das Ergebnis 8? Das würde mir wahrscheinlich helfen, loszulegen.
Antworten:
Mein Blog erklärt, was
reset
und was zushift
tun ist. Vielleicht möchten Sie das noch einmal lesen.Eine weitere gute Quelle, auf die ich auch in meinem Blog verweise, ist der Wikipedia-Eintrag zum Continuation-Passing-Stil . Dieser ist bei weitem der klarste zu diesem Thema, obwohl er keine Scala-Syntax verwendet, und die Fortsetzung wird explizit übergeben.
Das Papier über abgegrenzte Fortsetzungen, auf das ich in meinem Blog verweise, das aber anscheinend kaputt ist, enthält viele Anwendungsbeispiele.
Aber ich denke, das beste Beispiel für das Konzept der begrenzten Fortsetzung ist Scala Swarm. Darin stoppt die Bibliothek die Ausführung Ihres Codes an einem Punkt und die verbleibende Berechnung wird zur Fortsetzung. Die Bibliothek führt dann etwas aus - in diesem Fall überträgt sie die Berechnung auf einen anderen Host und gibt das Ergebnis (den Wert der Variablen, auf die zugegriffen wurde) an die Berechnung zurück, die gestoppt wurde.
Nun verstehen Sie nicht einmal das einfache Beispiel auf der Seite Scala, so kann mein Blog lesen. Darin geht es mir nur darum, diese Grundlagen zu erklären, warum das Ergebnis ist
8
.quelle
Ich fand die vorhandenen Erklärungen weniger effektiv bei der Erklärung des Konzepts als ich hoffen würde. Ich hoffe, dieser ist klar (und richtig). Ich habe noch keine Fortsetzungen verwendet.
Wenn eine Fortsetzungsfunktion
cf
aufgerufen wird:shift
Blocks und beginnt am Ende des Blocks erneutcf
wird vomshift
Block "ausgewertet", wenn die Ausführung fortgesetzt wird. Dies kann für jeden Anruf bei unterschiedlich seincf
reset
Blocks (oder bis zu einem Aufruf von) fortgesetztreset
wenn kein Block vorhanden ist).reset
Blocks (oder der Parameter toreset
(), wenn kein Block vorhanden ist) gibtcf
zurückcf
bis zum Ende desshift
Blocksreset
Blocks übersprungen (oder ein Aufruf zum Zurücksetzen?)Folgen Sie in diesem Beispiel den Buchstaben von A bis Z.
Dies druckt:
quelle
println(oneHundredOne) }
zuprintln(oneHundredOne); oneHundredOne }
.cannot compute type for CPS-transformed function result
Fehler zu vermeiden ,+1
muss sofort danach folgenoneHundredOne}
. Die Kommentare, die sich derzeit zwischen ihnen befinden, brechen irgendwie die Grammatik.Angesichts des kanonischen Beispiels aus dem Forschungsbericht zu Scalas begrenzten Fortsetzungen, das geringfügig modifiziert wurde, damit die Funktionseingabe
shift
den Namen erhältf
und somit nicht mehr anonym ist.Die Scala Plugin Transformationen dieses Beispiel derart , dass die Berechnung (innerhalb des Eingabearguments der
reset
) aus jedem Ausgangshift
den Aufrufreset
wird ersetzt mit der Funktion ( zum Beispielf
) eingegebenshift
.Die ersetzte Berechnung wird in eine Funktion verschoben (dh verschoben)
k
. Die Funktionf
gibt die Funktionk
, wok
enthält die Berechnung ersetzt,k
Eingängex: Int
, und die Berechnung ink
Ersetztshift(f)
mitx
.Welches hat den gleichen Effekt wie:
Beachten Sie, dass der Typ
Int
des Eingabeparametersx
(dh die Typensignatur vonk
) durch die Typensignatur des Eingabeparameters von angegeben wurdef
.Ein weiteres geliehenes Beispiel mit der konzeptionell äquivalenten Abstraktion, dh
read
die Funktionseingabe fürshift
:Ich glaube, dies würde in das logische Äquivalent von übersetzt werden:
Ich hoffe, dies verdeutlicht die kohärente gemeinsame Abstraktion, die durch die vorherige Präsentation dieser beiden Beispiele etwas verschleiert wurde. Zum Beispiel wurde das kanonische erste Beispiel in der Forschungsarbeit als anonyme Funktion anstelle meines Namens dargestellt
f
, so dass einigen Lesern nicht sofort klar war, dass es abstrakt analog zu demread
in der geliehenen war zweiten Beispiel war.So abgegrenzte Fortsetzungen erzeugen die Illusion einer Umkehrung der Kontrolle von "Sie rufen mich von außen an
reset
" zu "Ich rufe Sie von innen anreset
".Beachten Sie den Rückgabetyp
f
ist, aberk
ist nicht erforderlich , das gleiche wie der Rückgabetyp seinreset
, das heißtf
die Freiheit , jeden Rückgabetyp zu erklären , hat fürk
so langef
zurückkehren vom gleichen Typ wiereset
. Das Gleiche gilt fürread
undcapture
(siehe auchENV
unten).Begrenzte Fortsetzungen kehren die Kontrolle des Zustands nicht implizit um, z. B.
read
undcallback
sind keine reinen Funktionen. Somit kann der Aufrufer keine referenziell transparenten Ausdrücke erstellen und hat somit keine deklarative (auch als transparent bezeichnete) Kontrolle über die beabsichtigte imperative Semantik .Wir können explizit reine Funktionen mit begrenzten Fortsetzungen erreichen.
Ich glaube, dies würde in das logische Äquivalent von übersetzt werden:
Dies wird aufgrund der expliziten Umgebung laut.
Tangentialerweise hat Scala keine globale Typinferenz von Haskell und konnte meines Wissens das implizite Anheben einer staatlichen Monade
unit
(als eine mögliche Strategie zum Verbergen der expliziten Umgebung) nicht unterstützen, da Haskells globale Typinferenz (Hindley-Milner) hängt davon ab , dass die mehrfache virtuelle Vererbung von Diamanten nicht unterstützt wird .quelle
reset
/shift
geändert werdendelimit
/replace
. Und durch Konvention, dassf
undread
seinwith
, undk
undcallback
seinreplaced
,captured
,continuation
, odercallback
.replacement
stattdessen vorschlagenwith
. Afaik,()
ist auch erlaubt? Afaik{}
ist "Scalas leichtgewichtige Syntax für Schließungen" , die einen zugrunde liegenden Funktionsaufruf verbirgt . Sehen Sie sich zum Beispiel an, wie ich Daniels neu geschrieben habesequence
(beachten Sie, dass der Code nie kompiliert oder getestet wurde. Sie können mich also gerne korrigieren).shift
reset
sind Bibliotheksfunktionen, keine Schlüsselwörter. Somit{}
oder()
kann verwendet werden, wenn die Funktion nur einen Parameter erwartet . Scala hat By-name - Parameter (Abschnitt "9.5 Steuerung Abstraktionen" der Programmierung in Scala, 2nd ed sieht. Pg. 218), wo , wenn die Parameter des Typs() => ...
der() =>
beseitigt werden kann. Ich nehme anUnit
und nicht nach Namen, da der Block ausgewertet werden soll, bevor erreset
aufgerufen wird, aber ich benötige{}
mehrere Anweisungen. Meine Verwendung fürshift
ist korrekt, da offensichtlich ein Funktionstyp eingegeben wird.Die Fortsetzung erfasst den Status einer Berechnung, die später aufgerufen wird.
Stellen Sie sich die Berechnung zwischen dem Verlassen des Shift-Ausdrucks und dem Verlassen des Reset-Ausdrucks als Funktion vor. Innerhalb des Verschiebungsausdrucks heißt diese Funktion k, sie ist die Fortsetzung. Sie können es weitergeben und später sogar mehrmals aufrufen.
Ich denke, der vom Rücksetzausdruck zurückgegebene Wert ist der Wert des Ausdrucks innerhalb des Verschiebungsausdrucks nach dem =>, aber darüber bin ich mir nicht ganz sicher.
Mit Fortsetzungen können Sie also einen eher willkürlichen und nicht lokalen Code in eine Funktion einbinden. Dies kann verwendet werden, um nicht standardmäßige Kontrollabläufe wie Coroutining oder Backtracking zu implementieren.
Daher sollten Fortsetzungen auf Systemebene verwendet werden. Sie durch Ihren Anwendungscode zu streuen, wäre ein sicheres Rezept für Albträume, viel schlimmer als der schlechteste Spaghetti-Code, den goto jemals verwenden könnte.
Haftungsausschluss: Ich habe kein tiefgreifendes Verständnis der Fortsetzungen in Scala. Ich habe es nur aus dem Betrachten der Beispiele und dem Kennen der Fortsetzungen aus dem Schema abgeleitet.
quelle
Aus meiner Sicht wurde hier die beste Erklärung gegeben: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html
Eines der Beispiele:
quelle
Ein weiterer (neuerer - Mai 2016) Artikel über Scala-Fortsetzungen ist:
" Zeitreise in Scala: CPS in Scala (Fortsetzung von Scala) " von Shivansh Srivastava (
shiv4nsh
) .Es bezieht sich auch auf Jim McBeath 's Artikel in erwähnter Dmitry Bespalov ' s Antwort .
Aber vorher beschreibt es Fortsetzungen wie folgt:
Davon abgesehen , wie im April 2014 für Scala 2.11.0-RC1 angekündigt
quelle
Scala-Fortsetzungen anhand aussagekräftiger Beispiele
Definieren wir,
from0to10
dass die Idee der Iteration von 0 bis 10 ausgedrückt wird:Jetzt,
Drucke:
In der Tat brauchen wir nicht
x
:druckt das gleiche Ergebnis.
Und
druckt alle Paare:
Wie funktioniert das?
Es ist der genannte Code ,
from0to10
und die Telefonvorwahl . In diesem Fall folgt der folgende Blockreset
. Einer der an den aufgerufenen Code übergebenen Parameter ist eine Rücksprungadresse, die anzeigt, welcher Teil des aufrufenden Codes noch nicht ausgeführt wurde (**). Dieser Teil des aufrufenden Codes ist die Fortsetzung . Der aufgerufene Code kann mit diesem Parameter alles tun, wofür er sich entscheidet: Übergeben Sie die Kontrolle an ihn, ignorieren Sie ihn oder rufen Sie ihn mehrmals auf. Hier wirdfrom0to10
diese Fortsetzung für jede Ganzzahl im Bereich 0..10 aufgerufen.Aber wo endet die Fortsetzung? Dies ist wichtig, da der letzte
return
aus der Fortsetzung die Kontrolle an den aufgerufenen Code zurückgibtfrom0to10
. In Scala endet es dort, wo derreset
Block endet (*).Nun sehen wir, dass die Fortsetzung als deklariert ist
cont: Int => Unit
. Warum? Wir rufenfrom0to10
als aufval x = from0to10()
undInt
sind die Art von Wert, zu dem gehtx
.Unit
bedeutet, dass der Block danachreset
keinen Wert zurückgeben darf (andernfalls tritt ein Typfehler auf). Im Allgemeinen gibt es 4 Typensignaturen: Funktionseingabe, Fortsetzungseingabe, Fortsetzungsergebnis, Funktionsergebnis. Alle vier müssen mit dem Aufrufkontext übereinstimmen.Oben haben wir Wertepaare gedruckt. Drucken wir die Multiplikationstabelle. Aber wie geben wir
\n
nach jeder Zeile aus?Mit dieser Funktion
back
können wir festlegen, was zu tun ist, wenn die Steuerung zurückkehrt, von der Fortsetzung bis zum Code, der sie aufgerufen hat.back
ruft zuerst seine Fortsetzung auf und führt dann die Aktion aus .Es druckt:
Nun, jetzt ist es Zeit für ein paar Brain Twister. Es gibt zwei Anrufungen von
from0to10
. Was ist die Fortsetzung für die erstefrom0to10
? Es folgt dem Aufruf vonfrom0to10
im Binärcode , enthält aber im Quellcode auch die Zuweisungsanweisungval i =
. Es endet dort, wo derreset
Block endet, aber das Ende desreset
Blocks gibt die Kontrolle nicht an das erste zurückfrom0to10
. Das Ende desreset
Blocks gibt die Kontrolle an die 2. zurückfrom0to10
, die wiederum schließlich die Kontrolle an zurückgibtback
, und es ist dasback
, was die Kontrolle an den ersten Aufruf von zurückgibtfrom0to10
. Wenn der erste (ja! 1.!)from0to10
Beendet wird, wird der gesamtereset
Block verlassen.Eine solche Methode zur Rückgabe der Kontrolle wird als Backtracking bezeichnet . Es handelt sich um eine sehr alte Technik, die zumindest aus der Zeit der Prolog- und AI-orientierten Lisp-Derivate bekannt ist.
Die Namen
reset
undshift
sind Fehlbezeichnungen. Diese Namen sollten besser für die bitweisen Operationen belassen werden.reset
Definiert Fortsetzungsgrenzen undshift
nimmt eine Fortsetzung vom Aufrufstapel.Anmerkungen)
(*) In Scala endet die Fortsetzung dort, wo der
reset
Block endet. Ein anderer möglicher Ansatz wäre, es dort enden zu lassen, wo die Funktion endet.(**) Einer der Parameter des aufgerufenen Codes ist eine Rücksprungadresse, die anzeigt, welcher Teil des aufrufenden Codes noch nicht ausgeführt wurde. Nun, in Scala wird dafür eine Folge von Absenderadressen verwendet. Wie viele? Alle Rücksprungadressen, die seit dem Betreten des
reset
Blocks auf dem Aufrufstapel platziert wurden .UPD Teil 2 Fortsetzen verwerfen: Filtern
Dies druckt:
Lassen Sie uns zwei wichtige Operationen herausrechnen: die Fortsetzung verwerfen (
fail()
) und die Kontrolle an sie weitergeben (succ()
):Beide Versionen von
succ()
(oben) funktionieren. Es stellt sich heraus, dassshift
es eine lustige Signatur hat, und obwohlsucc()
es nichts tut, muss es diese Signatur für die Typbalance haben.wie erwartet wird gedruckt
Innerhalb einer Funktion
succ()
ist nicht erforderlich:wieder druckt es
Definieren wir nun
onOdd()
überonEven()
:Wenn oben gerade
x
ist, wird eine Ausnahme ausgelöst und die Fortsetzung wird nicht aufgerufen. Wenn diesx
ungerade ist, wird die Ausnahme nicht ausgelöst und die Fortsetzung wird aufgerufen. Der obige Code wird gedruckt:quelle