Warum sind Funktionen in F # und Ocaml (und möglicherweise in anderen Sprachen) nicht standardmäßig rekursiv?
Mit anderen Worten, warum haben die Sprachdesigner entschieden, dass es eine gute Idee ist, Sie explizit dazu zu bringen, rec
eine Erklärung einzugeben wie:
let rec foo ... = ...
und nicht standardmäßig die Funktion rekursiv geben? Warum ein explizites rec
Konstrukt?
Antworten:
Die französischen und britischen Nachkommen der ursprünglichen ML haben unterschiedliche Entscheidungen getroffen und ihre Entscheidungen wurden im Laufe der Jahrzehnte an die modernen Varianten vererbt. Das ist also nur ein Vermächtnis, aber es beeinflusst die Redewendungen in diesen Sprachen.
Funktionen sind in der französischen CAML-Sprachfamilie (einschließlich OCaml) standardmäßig nicht rekursiv. Diese Auswahl erleichtert das Ersetzen von Funktions- (und Variablen-) Definitionen mithilfe von
let
in diesen Sprachen, da Sie im Hauptteil einer neuen Definition auf die vorherige Definition verweisen können. F # hat diese Syntax von OCaml geerbt.Beispiel: Ersetzen der Funktion
p
beim Berechnen der Shannon-Entropie einer Sequenz in OCaml:Beachten Sie, wie das Argument
p
für dieshannon
Funktion höherer Ordnungp
in der ersten Zeile des Körpers durch ein anderes und dann durch ein anderes ersetzt wirdp
in der zweiten Zeile des Körpers wird.Umgekehrt hat der britische SML-Zweig der ML-Sprachfamilie die andere Wahl getroffen, und die an SML
fun
gebundenen Funktionen sind standardmäßig rekursiv. Wenn die meisten Funktionsdefinitionen keinen Zugriff auf vorherige Bindungen ihres Funktionsnamens benötigen, führt dies zu einfacherem Code. Allerdings sind superceded Funktionen aus verschiedenen Namen zu verwenden (f1
,f2
usw.) , die den Umfang verpestet und macht es möglich, versehentlich invoke die falsche „Version“ einer Funktion. Und es gibt jetzt eine Diskrepanz zwischen implizit rekursivfun
gebundenen Funktionen und nicht rekursivval
gebundenen Funktionen.Haskell ermöglicht es, die Abhängigkeiten zwischen Definitionen abzuleiten, indem sie auf ihre Reinheit beschränkt werden. Dies lässt Spielzeugmuster einfacher aussehen, ist aber an anderer Stelle mit hohen Kosten verbunden.
Beachten Sie, dass die Antworten von Ganesh und Eddie rote Heringe sind. Sie erklärten, warum Gruppen von Funktionen nicht in einem Riesen platziert werden können,
let rec ... and ...
da dies Auswirkungen darauf hat, wann Typvariablen verallgemeinert werden. Dies hat nichts mitrec
der Standardeinstellung in SML zu tun , nicht jedoch in OCaml.quelle
Ein entscheidender Grund für die explizite Verwendung von
rec
ist die Hindley-Milner-Typinferenz, die allen statisch typisierten funktionalen Programmiersprachen zugrunde liegt (wenn auch auf verschiedene Weise geändert und erweitert).Wenn Sie eine Definition haben
let f x = x
, erwarten Sie, dass sie einen Typ hat'a -> 'a
und'a
an verschiedenen Stellen auf verschiedene Typen anwendbar ist . Aber ebenso, wenn Sie schreibenlet g x = (x + 1) + ...
, würden Sie erwartenx
, alsint
im Rest des Körpers von behandelt zu werdeng
.Die Art und Weise, wie die Hindley-Milner-Folgerung mit dieser Unterscheidung umgeht, erfolgt durch einen expliziten Verallgemeinerungsschritt . An bestimmten Stellen bei der Verarbeitung Ihres Programms stoppt das Typsystem und sagt "OK, die Typen dieser Definitionen werden an dieser Stelle verallgemeinert, sodass alle freien Typvariablen in ihrem Typ frisch sind , wenn jemand sie verwendet instanziiert werden und somit wird keine anderen Verwendungen dieser Definition stören. "
Es stellt sich heraus, dass der sinnvolle Ort für diese Verallgemeinerung die Überprüfung eines gegenseitig rekursiven Satzes von Funktionen ist. Früher, und Sie verallgemeinern zu viel, was zu Situationen führt, in denen Typen tatsächlich kollidieren könnten. Später, und Sie werden zu wenig verallgemeinern und Definitionen erstellen, die nicht mit Instanzen von mehreren Typen verwendet werden können.
Was kann der Typprüfer tun, wenn er wissen muss, welche Definitionssätze sich gegenseitig rekursiv sind? Eine Möglichkeit besteht darin, einfach eine Abhängigkeitsanalyse für alle Definitionen in einem Bereich durchzuführen und sie in die kleinstmöglichen Gruppen zu ordnen. Haskell tut dies tatsächlich, aber in Sprachen wie F # (und OCaml und SML), die uneingeschränkte Nebenwirkungen haben, ist dies eine schlechte Idee, da möglicherweise auch die Nebenwirkungen neu angeordnet werden. Stattdessen wird der Benutzer aufgefordert, explizit zu markieren, welche Definitionen sich gegenseitig rekursiv sind, und somit durch Erweiterung, wo eine Verallgemeinerung erfolgen soll.
quelle
rec
, SML jedoch nicht, ist ein offensichtliches Gegenbeispiel. Wenn aus den von Ihnen beschriebenen Gründen Typinferenz das Problem gewesen wäre, hätten OCaml und SML nicht unterschiedliche Lösungen wählen können. Der Grund ist natürlich, dass Sie darüber sprechenand
, um Haskell relevant zu machen.Es gibt zwei Hauptgründe, warum dies eine gute Idee ist:
Wenn Sie rekursive Definitionen aktivieren, können Sie zunächst nicht auf eine vorherige Bindung eines gleichnamigen Werts verweisen. Dies ist oft eine nützliche Redewendung, wenn Sie beispielsweise ein vorhandenes Modul erweitern.
Zweitens sind rekursive Werte und insbesondere Sätze von gegenseitig rekursiven Werten viel schwerer zu begründen als Definitionen, die in der richtigen Reihenfolge ablaufen, wobei jede neue Definition auf dem aufbaut, was bereits definiert wurde. Es ist schön, beim Lesen eines solchen Codes die Garantie zu haben, dass neue Definitionen mit Ausnahme von Definitionen, die explizit als rekursiv gekennzeichnet sind, nur auf vorherige Definitionen verweisen können.
quelle
Einige Vermutungen:
let
wird nicht nur zum Binden von Funktionen verwendet, sondern auch von anderen regulären Werten. Die meisten Werteformen dürfen nicht rekursiv sein. Bestimmte Formen rekursiver Werte sind zulässig (z. B. Funktionen, verzögerte Ausdrücke usw.). Daher ist eine explizite Syntax erforderlich, um dies anzuzeigen.let
Konstrukt ähnelt demlet
Konstrukt in Lisp und Scheme; die nicht rekursiv sind.letrec
In Schema gibt es ein separates Konstrukt für rekursive Lasst unsquelle
let rec xs = 0::ys and ys = 1::xs
.Angesichts dessen:
Vergleichen Sie:
Mit diesem:
Ersteres definiert neu
f
, um das zuvor definiertef
auf das Ergebnis der Anwendungg
auf anzuwendena
. Letztere Redefinesf
Schleife immer Anwendungg
zua
, was in der Regel nicht , was Sie wollen in ML - Varianten.Das heißt, es ist eine Sache im Stil eines Sprachdesigners. Mach einfach mit.
quelle
Ein großer Teil davon ist, dass der Programmierer mehr Kontrolle über die Komplexität seiner lokalen Bereiche hat. Das Spektrum
let
,let*
undlet rec
bietet ein zunehmendes Maß an Stromversorgung und Kosten.let*
undlet rec
sind im Wesentlichen verschachtelte Versionen des einfachenlet
, so dass die Verwendung einer der beiden teurer ist. Mit dieser Einstufung können Sie die Optimierung Ihres Programms mikromanagen, indem Sie auswählen, welche Ebene Sie für die jeweilige Aufgabe benötigen. Wenn Sie keine Rekursion benötigen oder nicht auf frühere Bindungen verweisen können, können Sie auf eine einfache Erlaubnis zurückgreifen, um ein wenig Leistung zu sparen.Es ähnelt den abgestuften Gleichheitsprädikaten in Schema. ( das heißt
eq?
,eqv?
undequal?
)quelle