Ist Rekursion in Scala sehr notwendig?

7

Im Kursleiter-Scala-Tutorial verwenden die meisten Beispiele Top-Down-Iterationen. Teilweise, wie ich sehen kann, werden Iterationen verwendet, um for / while-Schleifen zu vermeiden. Ich komme aus C ++ und fühle mich ein wenig verwirrt.

Wird Iteration für / while-Schleifen gewählt? Ist es in der Produktion praktisch? Irgendein Risiko des stackoverflow? Wie steht es mit Effizienz? Wie wäre es mit der Bottom-Up-Dynamic-Programmierung (besonders wenn es sich nicht um eine Tail-Recusion handelt)?

Soll ich weniger "if" -Bedingungen verwenden, stattdessen mehr "case" und Unterklassen?

    
galaapples 05.09.2013, 21:03
quelle

5 Antworten

16

Wirklich hochwertige Scala wird sehr wenig Iteration und nur etwas mehr Rekursion verwenden. Was mit Schleifen in untergeordneten Imperativsprachen gemacht werden sollte, wird normalerweise am besten mit den Kombinatoren höherer Ordnung durchgeführt, insbesondere mit map und flatmap, aber auch filter, zip, fold, foreach, reduzieren, sammeln, partitionieren, scannen, groupBy und a gute paar andere. Iteration wird am besten nur in leistungskritischen Abschnitten durchgeführt, und Rekursion nur in einigen Fällen, in denen die Kombinatoren höherer Ordnung nicht ganz passen (was normalerweise nicht Tail rekursiv ist, fwiw). In drei Jahren Scala-Codierung in Produktionssystemen verwendete ich Iteration einmal, Rekursion zweimal und Karte ungefähr fünfmal pro Tag.

    
Dave Griffith 06.09.2013, 02:28
quelle
6

Hmm, mehrere Fragen in einem.

Notwendigkeit der Rekursion

  1. Rekursion ist nicht notwendig, kann aber manchmal eine sehr elegante Lösung bieten.
  2. Wenn die Lösung tail recursive ist und der Compiler Tail Call Optimierung , dann kann die Lösung sogar effizient sein.
  3. Wie schon gesagt, hat Scala viele Kombinatorfunktionen, mit denen dieselben Aufgaben expressiver und effizienter ausgeführt werden können.

Ein klassisches Beispiel ist das Schreiben einer Funktion zum Zurückgeben der n-ten Fibonacci-Nummer . Hier ist eine naive rekursive Implementierung:

%Vor%

Nun, das ist ineffizient (definitiv nicht tail rekursiv), aber es ist sehr offensichtlich, wie sich seine Struktur auf die Fibonacci-Sequenz bezieht. Wir können es aber richtig rekursiv machen:

%Vor%

Das hätte kürzer geschrieben werden können, aber es ist eine effiziente rekursive Implementierung. Das heißt, es ist nicht so schön wie das erste und seine Struktur ist weniger eindeutig auf das ursprüngliche Problem bezogen.

Endlich, schamlos gestohlen von woanders auf dieser Seite ist Luigi Plinges Stream-basierte Implementierung:

%Vor%

Sehr knapp, effizient, elegant und (wenn Sie Streams und faule Bewertung verstehen) sehr ausdrucksvoll. Es ist auch rekursiv; #:: ist eine rekursive Funktion, aber eine, die in einem lazy-evaluated Kontext arbeitet. Sie müssen sicherlich in der Lage sein, rekursiv zu denken, um diese Art von Lösung zu finden.

Iteration im Vergleich zu For / While-Schleifen

Ich gehe davon aus, dass Sie hier den traditionellen C-Style für meinen.

Rekursive Lösungen können oft while -Schleifen vorgezogen werden, weil C / C ++ / Java-ähnliche while-Schleifen keinen Wert zurückgeben und Nebenwirkungen erfordern, um irgendetwas zu erreichen (dies gilt auch für C-Style) für und Java-style foreach ). Ehrlich gesagt wünschte ich mir oft, Scala hätte while nie umgesetzt (oder es als syntaktischen Zucker für so etwas wie Schemes Namens Let implementiert), weil es klassisch ausgebildeten Java-Entwicklern erlaubt, die Dinge so zu tun, wie sie es immer taten . Da sind Situationen, in denen eine Schleife mit Nebeneffekten, die während Ihnen gibt, eine expressivere Art ist etwas zu erreichen, aber ich hatte eher Java-fixierte Devs gezwungen etwas härter dafür (zB durch Missbrauch eines zum Verständnis ).

Einfach, traditionelle während und für macht clunky imperative Codierung viel zu einfach. Wenn Sie sich nicht darum kümmern, warum benutzen Sie Scala?

Effizienz und Risiko von Stackoverflow

Die Tail-Optimierung eliminiert das Risiko von Stackoverflow. Wenn Sie rekursive Lösungen rekursiv rekursiv schreiben, können sie sehr hässlich werden (insbesondere in jeder Sprache, die auf der JVM ausgeführt wird).

Rekursive Lösungen können effizienter sein als zwingendere Lösungen, manchmal überraschend. Ein Grund ist, dass sie oft auf Listen operieren, und zwar nur auf Kopf und Tail Zugriff. Kopf- und Fußzeilenoperationen in Listen sind tatsächlich schneller als Operationen mit wahlfreiem Zugriff in strukturierteren Sammlungen.

Dynamische Programmierung

Ein guter rekursiver Algorithmus reduziert typischerweise ein komplexes Problem auf eine kleine Menge einfacherer Probleme, wählt einen zu lösenden aus und delegiert den Rest an eine andere Funktion (normalerweise ein rekursiver Aufruf an sich selbst). Nun, für mich hört sich das nach einer guten Eignung für dynamische Programmierung an. Sicher, wenn ich einen rekursiven Ansatz für ein Problem versuche, beginne ich oft mit einer naiven Lösung, von der ich weiß, dass sie nicht jeden Fall lösen kann, wo sie fehlschlägt, dieses Muster zur Lösung hinzufügt und zum Erfolg iteriert.

The Little Schemer hat viele Beispiele dieses iterativen Ansatzes für die rekursive Programmierung, insbesondere weil es re - verwendet frühere Lösungen als Unterkomponenten für spätere, komplexere. Ich würde sagen, es ist der Inbegriff des Ansatzes der dynamischen Programmierung. (Es ist auch eines der am besten geschriebenen Lehrbücher über Software, die jemals produziert wurden). Ich kann es empfehlen, nicht zuletzt weil es dir Scheme zur gleichen Zeit beibringt. Wenn Sie Scheme nicht wirklich lernen wollen (warum? Warum nicht?), Wurde es für einige andere angepasst Sprachen

Wenn versus Übereinstimmung

if Ausdrücke in Scala geben Werte zurück (was sehr nützlich ist und warum Scala keinen ternären Operator benötigt). Es gibt keinen Grund, einfach zu vermeiden

%Vor%

Ausdrücke.Der Hauptgrund für die Übereinstimmung anstelle einer einfachen if ... else besteht darin, die Möglichkeiten von case-Anweisungen zu nutzen, um Informationen aus komplexen Objekten zu extrahieren. Hier ist ein Beispiel.

Auf der anderen Seite ist if ... else if ... else if ... else ein schreckliches Muster

  1. Es gibt keinen einfachen Weg zu sehen, ob Sie alle Möglichkeiten richtig abgedeckt haben, sogar mit einem endgültigen else .
  2. Ungewollt verschachtelte wenn Ausdrücke schwer zu erkennen sind
  3. Es ist zu einfach, nicht zusammenhängende Bedingungen miteinander zu verknüpfen (zufällig oder durch ein Design mit Knochen)

Wo immer Sie finden, dass Sie sonst, wenn geschrieben haben, suchen Sie nach einer Alternative. match ist ein guter Anfang.

    
itsbruce 06.09.2013 13:00
quelle
4

Ich gehe davon aus, dass Sie in Ihrer Frage "Rekursion" meinen, weil Sie "Rekursion" sagen, und nicht "Iteration" (die nicht für "over / while loops" gewählt werden kann) iterativ: D).

Vielleicht interessiert es Sie, Effective Scala zu lesen, insbesondere den Abschnitt über Kontrollstrukturen, der meist Ihre Frage beantworten sollte. Kurz gesagt:

Rekursion ist nicht "besser" als Iteration. Oft ist es einfacher, einen rekursiven Algorithmus für ein gegebenes Problem zu schreiben, dann einen iterativen Algorithmus zu schreiben (natürlich gibt es Fälle, in denen das Gegenteil zutrifft). Wenn "Tail Call Optimization" auf ein Problem angewendet werden kann, konvertiert der Compiler es tatsächlich in einen iterativen Algorithmus, was es unmöglich macht, dass ein StackOverflow ohne Leistungseinbußen auftritt. In Effective Scala können Sie auch über die Tail-Call-Optimierung lesen.

Das Hauptproblem Ihrer Frage ist, dass sie sehr ist. Es gibt viele verfügbare Ressourcen für funktionale Programmierung, idiomatische Scala, dynamische Programmierung und so weiter und keine Antwort hier auf Stack Overflow wäre in der Lage, alle diese Themen zu behandeln. Es wäre wahrscheinlich eine gute Idee, die Interwebs eine Weile zu durchstreifen und dann mit konkreteren Fragen zurückzukommen :)

    
fresskoma 05.09.2013 22:27
quelle
1

Einer der Hauptvorteile der Rekursion besteht darin, dass Sie Lösungen ohne Mutation erstellen können. Für folgendes Beispiel müssen Sie die Summe aller Elemente einer Liste berechnen.

Einer der vielen Wege, dieses Problem zu lösen, ist wie folgt. Die imperative Lösung für dieses Problem verwendet For-Schleife wie folgt:

%Vor%

Und Rekursionslösung würde folgendermaßen aussehen:

%Vor%

Der Unterschied besteht darin, dass eine rekursive Lösung keine veränderbaren temporären Variablen verwendet, indem Sie das Problem in kleinere Teile aufteilen. Da es bei der funktionalen Programmierung nur darum geht, side-effect-freie Programme zu schreiben, ist es immer ratsam, Rekursion vs Schleifen (die mutierende Variablen verwenden) zu verwenden.

Die Kopfrekursion ist eine traditionelle Methode der Rekursion, bei der Sie zuerst den rekursiven Aufruf durchführen und dann den Rückgabewert von der rekursiven Funktion übernehmen und das Ergebnis berechnen.

Im Allgemeinen wird beim Aufruf einer Funktion ein Eintrag zum Aufruf-Stack eines aktuell laufenden Threads hinzugefügt. Der Nachteil ist, dass der Aufrufstapel eine definierte Größe hat, so dass Sie die StackOverflowError-Ausnahme schnell erhalten können. Deshalb zieht Java es vor, eher zu iterieren als zu rekurrieren. Da Scala auf der JVM läuft, leidet auch Scala an diesem Problem. Aber mit Scala 2.8.1 beginnt Scala diese Einschränkung durch die Tail Call Optimierung. Sie können eine Tail-Rekursion in Scala durchführen.

Rekapitulation ist der bevorzugte Weg in der funktionalen Programmierung, um die Verwendung von Mutationen zu vermeiden und zweitens wird die Tail-Rekursion in Scala unterstützt, so dass Sie nicht in StackOverFlow-Exceptions gelangen, die Sie in Java erhalten.

Hoffe, das hilft.

    
Vikas Pandya 06.09.2013 01:56
quelle
0

Was den Stack-Überlauf anbelangt, so können Sie oft mit der Beseitigung von Tail Calls davonkommen.

Der Grund, warum scala und andere Funktionsparadigmen Schleifen für / while vermeiden, hängt stark von Zustand und Zeit ab. Das macht es viel schwieriger, über komplexe "Schleifen" in einem formalen und präzisen Manuskript nachzudenken.

    
WorBlux 06.09.2013 02:05
quelle