Die Definition eines Y-Kombinators in F # lautet
let rec y f x = f (y f) x
Als erstes Argument erwartet f eine Fortsetzung der rekursiven Teilprobleme. Wenn wir das yf als Fortsetzung verwenden, sehen wir, dass f auf aufeinander folgende Aufrufe angewendet wird, sobald wir uns entwickeln können
let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...
Das Problem ist, dass dieses Schema a priori die Verwendung von Tail-Call-Optimierungen ausschließt: In der Tat könnte eine Operation in den fs anstehen. In diesem Fall können wir den lokalen Stapelrahmen, der mit f assoziiert ist, nicht einfach mutieren.
So :
- Zum einen erfordert die Verwendung des Y-Kombinators eine explizit andere Fortsetzung als die eigentliche Funktion.
- Wenn Sie TCO anwenden möchten, möchten wir, dass in f keine Operation ansteht und nur f selbst aufgerufen wird.
Kennen Sie eine Möglichkeit, diese beiden zu versöhnen? Wie ein Y mit Akkumulator-Trick oder ein Y mit CPS-Trick? Oder ein Argument, das beweist, dass es unmöglich ist, es zu tun?
f#
recursion
tail-call
continuation
nicolas
quelle
quelle
f
. Wir können sehen, dassy
dasf
mit einem Thunk klappen könnte(y f)
, aber wie Sie sagen,f
könnte eine Operation anstehen. Ich denke, es wäre interessant zu wissen, ob es einen separaten Kombinator gibt, der die Rückrufe erleichtert. Ich frage mich, ob diese Frage auf der CS Stackexchange-Website mehr Beachtung finden würde.Antworten:
Nein, und das aus gutem Grund, IMHO.
Der Y-Kombinator ist ein theoretisches Konstrukt und wird nur benötigt, um die Lambda-Berechnung zu vervollständigen.
Daher ist der Y-Kombinator wirklich faszinierend.
Aber : Niemand benutzt den Y-Kombinator tatsächlich zur Rekursion! (Außer vielleicht zum Spaß, um zu zeigen, dass es wirklich funktioniert.)
Die Tail-Call-Optimierung OTOH ist, wie der Name schon sagt, eine Optimierung. Es trägt nichts zur Ausdruckskraft einer Sprache bei, sondern ist nur aufgrund praktischer Überlegungen wie Stapelspeicher und Leistung von rekursivem Code wichtig.
Ihre Frage lautet also: Gibt es Hardware-Unterstützung für die Beta-Reduzierung? (Mit der Beta-Reduzierung werden Lambda-Ausdrücke reduziert, wissen Sie.) Aber keine funktionale Sprache (soweit mir bekannt ist) kompiliert ihren Quellcode zu einer Darstellung von Lambda-Ausdrücken, die zur Laufzeit Beta-reduziert werden.
quelle
Ich bin mir bei dieser Antwort nicht ganz sicher, aber es ist das Beste, was ich mir einfallen lassen konnte.
Der y-Kombinator ist von Natur aus faul, in strengen Sprachen muss die Faulheit manuell durch zusätzliche Lambdas hinzugefügt werden.
Ihre Definition sieht so aus, als ob zum Beenden Faulheit erforderlich ist, oder das
(y f)
Argument würde die Auswertung nie beenden und müsste evaluiert werden, ob es verwendet wird oder nichtf
. TOC in einem faulen Kontext ist komplizierter und darüber hinaus das Ergebnis einer(y f)
wiederholten Funktionszusammensetzung ohne Anwendung mit demx
. Ich bin mir nicht sicher, ob hierfür O (n) -Speicher erforderlich ist, wobei n die Tiefe der Rekursion ist, aber ich bezweifle, dass Sie dieselbe Art von Inhaltsverzeichnis erzielen können, die mit so etwas möglich ist (Wechsel zu Haskell, weil ich es eigentlich nicht weiß F #)Wenn Sie sich dessen noch nicht bewusst sind, kann der Unterschied zwischen
foldl
undfoldl'
in Haskell Aufschluss über die Situation geben.foldl
ist so geschrieben, als würde man es in einer eifrigen Sprache tun. Es ist jedoch schlimmer, alsfoldr
dass der Akku einen potenziell enormen Thunk speichert, der nicht teilweise ausgewertet werden kann. (Dies hängt damit zusammen, warum sowohl foldl als auch foldl 'nicht für unendliche Listen funktionieren.) Daher wurde in neueren Versionen von Haskellfoldl'
hinzugefügt, dass die Bewertung des Akkumulators jedes Mal erzwungen wird, wenn die Funktion wiederholt wird, um sicherzustellen, dass kein enormer Thunk erstellt wird. Ich bin sicher, dass http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 dies besser erklären kann als ich.quelle