Denken Sie daran: Funktionale Programmierung ist keine Programmierung mit Funktionen! ! !
23.4 Funktionale Programmierung
23.4.1 Was ist funktionale Programmierung?
Was ist funktionale Programmierung? Wenn Sie so unverblümt fragen, werden Sie feststellen, dass es sich um ein Konzept handelt, das nicht leicht zu erklären ist. Viele Veteranen mit langjähriger Erfahrung auf dem Gebiet der Programmierung können nicht klar erklären, was funktionale Programmierung studiert. Funktionale Programmierung ist in der Tat ein unbekanntes Gebiet für Programmierer, die mit prozeduraler Programmierung vertraut sind. Die Konzepte von Abschluss, Fortsetzung und Currying scheinen uns so unbekannt zu sein. Das vertraute Wenn, Sonst und Während haben nichts gemeinsam. Obwohl die funktionale Programmierung über wunderschöne mathematische Prototypen verfügt, mit denen die prozedurale Programmierung nicht mithalten kann, ist sie so mysteriös, dass nur diejenigen mit einem Doktortitel sie beherrschen können.
Tipp: Dieser Abschnitt ist etwas schwierig, aber es ist keine notwendige Fähigkeit, um JavaScript zu beherrschen, wenn Sie JavaScript nicht verwenden möchten, um die in Lisp ausgeführten Aufgaben auszuführen, oder die esoterischen Fähigkeiten von nicht erlernen möchten Die funktionale Programmierung können Sie überspringen und mit dem nächsten Kapitel Ihrer Reise fortfahren.
Zurück zur Frage: Was ist funktionale Programmierung? Die Antwort ist lang…
Das erste Gesetz der funktionalen Programmierung: Funktionen sind vom ersten Typ.
Wie ist dieser Satz selbst zu verstehen? Was ist ein echter Typ Eins? Schauen wir uns die folgenden mathematischen Konzepte an:
Binärgleichung F(x, y) = 0, x, y sind Variablen, schreiben Sie sie als y = f(x), x ist ein Parameter, y ist der Rückgabewert, f ist von x zu y Die Zuordnungsbeziehung wird als Funktion bezeichnet. Wenn G(x, y, z) = 0 oder z = g(x, y) ist, ist g die Abbildungsbeziehung von x, y nach z und auch eine Funktion. Wenn die Parameter x und y von g die vorherige Beziehung y = f(x) erfüllen, dann erhalten wir z = g(x, y) = g(x, f(x)). x) ist eine Funktion auf x und ein Parameter der Funktion g. Zweitens ist g eine Funktion höherer Ordnung als f.
Auf diese Weise verwenden wir z = g(x, f(x)), um die zugehörige Lösung der Gleichungen F(x, y) = 0 und G(x, y, z) = 0 darzustellen, die eine iterative Funktion ist . Wir können g auch in einer anderen Form ausdrücken, denken Sie an z = g(x, y, f), sodass wir die Funktion g in eine Funktion höherer Ordnung verallgemeinern. Im Vergleich zur vorherigen besteht der Vorteil der letzteren Darstellung darin, dass es sich um ein allgemeineres Modell handelt, beispielsweise um die zugehörige Lösung von T(x,y) = 0 und G(x,y,z) = 0. Wir haben es auch kann in der gleichen Form ausgedrückt werden (es sei einfach f=t). In diesem Sprachsystem, das die iterative Umwandlung der Lösung eines Problems in eine Funktion höherer Ordnung unterstützt, wird die Funktion als „erster Typ“ bezeichnet.
Funktionen in JavaScript sind eindeutig vom „ersten Typ“. Hier ist ein typisches Beispiel:
Array.prototype.each = Funktion(Abschluss)
{
return this.length ? [closure(this[0])].concat(this.slice(1).each(closure)) : [];
}
Dies ist wirklich ein magischer Zaubercode, der den Charme des funktionalen Stils voll zur Geltung bringt. Der gesamte Code enthält nur Funktionen und Symbole. Es ist einfach in der Form und unendlich kraftvoll.
[1,2,3,4].each(function(x){return x * 2}) erhält [2,4,6,8], während [1,2,3,4].each(function(x ){return x-1}) erhält [0,1,2,3].
Die Essenz von funktional und objektorientiert ist, dass „Tao der Natur folgt“. Wenn objektorientiert eine Simulation der realen Welt ist, dann ist der funktionale Ausdruck eine Simulation der mathematischen Welt. In gewissem Sinne ist sein Abstraktionsniveau höher als objektorientiert, da mathematische Systeme von Natur aus Merkmale aufweisen, die in der Natur unvergleichlich sind. der Abstraktion.
Das zweite Gesetz der funktionalen Programmierung: Abschlüsse sind der beste Freund der funktionalen Programmierung.
Abschlüsse sind, wie wir in den vorherigen Kapiteln erläutert haben, für die funktionale Programmierung sehr wichtig. Das größte Merkmal besteht darin, dass Sie von der inneren Ebene aus direkt auf die äußere Umgebung zugreifen können, ohne Variablen (Symbole) zu übergeben. Dies bringt großen Komfort für funktionale Programme unter mehrfacher Verschachtelung. Hier ist ein Beispiel:
(Funktion äußererFun(x)).
{
Rückgabefunktion innerFun(y)
{
return x * y;
}
})(2)(3);
Das dritte Gesetz der funktionalen Programmierung: Funktionen können Currying sein.
Was ist Currying? Es ist ein interessantes Konzept. Beginnen wir mit der Mathematik: Wir sagen, wir betrachten eine dreidimensionale Raumgleichung F(x, y, z) = 0, wenn wir z = 0 begrenzen, dann erhalten wir F(x, y, 0) = 0, bezeichnet als F '(x, y). Hier ist F' offensichtlich eine neue Gleichung, die die zweidimensionale Projektion der dreidimensionalen Raumkurve F(x, y, z) auf die z = 0-Ebene darstellt. Bezeichne y = f(x, z), sei z = 0, wir erhalten y = f(x, 0), bezeichne es als y = f'(x), wir sagen, dass die Funktion f' eine Currying-Lösung von f ist .
Nachfolgend finden Sie ein Beispiel für JavaScript-Currying:
Funktion add(x, y)
{
if(x!=null && y!=null) return x + y;
sonst if(x!=null && y==null) return function(y)
{
gib x + y zurück;
}
sonst if(x==null && y!=null) return function(x)
{
gib x + y zurück;
}
}
var a = add(3, 4);
var b = add(2);
var c = b(10);
Im obigen Beispiel führt b=add(2) zu einer Currying-Funktion von add(), die eine Funktion des Parameters y ist, wenn x = 2. Beachten Sie, dass sie auch oben verwendet wird von Schließungen.
Interessanterweise können wir Currying für jede Funktion verallgemeinern, zum Beispiel:
Funktion Foo(x, y, z, w)
{
var args = arguments;
if(Foo.length < args.length)
Rückgabefunktion()
{
zurückkehren
args.callee.apply(Array.apply([], args).concat(Array.apply([], arguments)));
}
anders
return x + y – z * w;
}
Das vierte Gesetz der funktionalen Programmierung: verzögerte Auswertung und Fortsetzung.
//TODO: Denken Sie hier noch einmal darüber nach
23.4.2 Vorteile des
Unit-Tests
der funktionalen ProgrammierungJedes Symbol der strengen funktionalen Programmierung ist ein Verweis auf eine direkte Menge oder ein direktes Ausdrucksergebnis, und keine Funktion hat Nebenwirkungen. Weil der Wert niemals irgendwo geändert wird und keine Funktion eine Größe außerhalb ihres Gültigkeitsbereichs ändert, die von anderen Funktionen (z. B. Klassenmitgliedern oder globalen Variablen) verwendet wird. Das bedeutet, dass das Ergebnis einer Funktionsauswertung nur ihr Rückgabewert ist und die einzigen Dinge, die ihren Rückgabewert beeinflussen, die Parameter der Funktion sind.
Das ist der feuchte Traum eines jeden Unit-Testers. Für jede Funktion im zu testenden Programm müssen Sie sich nur um ihre Parameter kümmern, ohne die Reihenfolge der Funktionsaufrufe berücksichtigen oder den externen Status sorgfältig festlegen zu müssen. Sie müssen lediglich Parameter übergeben, die Randfälle darstellen. Wenn jede Funktion im Programm den Unit-Test besteht, haben Sie großes Vertrauen in die Qualität der Software. Aber imperative Programmierung kann nicht so optimistisch sein. In Java oder C++ reicht es nicht aus, nur den Rückgabewert einer Funktion zu überprüfen – wir müssen auch den externen Status überprüfen, den die Funktion möglicherweise geändert hat.
Debuggen
Wenn sich ein funktionsfähiges Programm nicht wie erwartet verhält, ist das Debuggen ein Kinderspiel. Da Fehler in Funktionsprogrammen vor der Ausführung nicht von Codepfaden abhängen, die nichts mit ihnen zu tun haben, können auftretende Probleme jederzeit reproduziert werden. In imperativen Programmen treten Fehler auf und verschwinden, da die Funktion der Funktion von den Nebenwirkungen anderer Funktionen abhängt und Sie möglicherweise lange Zeit in Richtungen suchen, die nichts mit dem Auftreten des Fehlers zu tun haben, jedoch ohne Ergebnisse. Dies ist bei funktionalen Programmen nicht der Fall. Wenn das Ergebnis einer Funktion falsch ist, wird die Funktion unabhängig davon, was Sie zuvor ausführen, immer das gleiche falsche Ergebnis zurückgeben.
Sobald Sie das Problem reproduzieren, wird es mühelos sein, seine Grundursache zu finden, und Sie werden vielleicht sogar glücklich sein. Unterbrechen Sie die Ausführung dieses Programms und untersuchen Sie den Stapel. Wie bei der imperativen Programmierung werden Ihnen die Parameter jedes Funktionsaufrufs auf dem Stapel angezeigt. In imperativen Programmen reichen diese Parameter jedoch nicht aus. Funktionen hängen auch von Mitgliedsvariablen, globalen Variablen und dem Status der Klasse ab (der wiederum von vielen davon abhängt). Bei der funktionalen Programmierung hängt eine Funktion nur von ihren Parametern ab, und diese Informationen liegen direkt vor Ihren Augen! Außerdem können Sie in einem imperativen Programm allein durch die Überprüfung des Rückgabewerts einer Funktion nicht sicherstellen, dass die Funktion ordnungsgemäß funktioniert. Zur Bestätigung müssen Sie den Status von Dutzenden von Objekten außerhalb des Gültigkeitsbereichs dieser Funktion überprüfen. Bei einem funktionsfähigen Programm müssen Sie sich nur den Rückgabewert ansehen!
Überprüfen Sie die Parameter und Rückgabewerte der Funktion entlang des Stapels. Sobald Sie ein unzumutbares Ergebnis finden, geben Sie diese Funktion ein und wiederholen Sie diesen Vorgang, bis Sie den Punkt finden, an dem der Fehler generiert wird.
Parallele Funktionsprogramme können ohne Änderung parallel ausgeführt werden. Machen Sie sich keine Sorgen über Deadlocks und kritische Abschnitte, da Sie niemals Sperren verwenden! Keine Daten in einem Funktionsprogramm werden zweimal von demselben Thread geändert, geschweige denn von zwei verschiedenen Threads. Dies bedeutet, dass Threads einfach und ohne Bedenken hinzugefügt werden können, ohne die herkömmlichen Probleme zu verursachen, die parallele Anwendungen plagen.
Wenn dies der Fall ist, warum nutzt dann nicht jeder die funktionale Programmierung in Anwendungen, die hochgradig parallele Vorgänge erfordern? Nun, das tun sie. Ericsson hat eine funktionale Sprache namens Erlang entwickelt und sie in Telekommunikationsschaltern verwendet, die eine extrem hohe Fehlertoleranz und Skalierbarkeit erfordern. Viele Menschen haben auch die Vorteile von Erlang entdeckt und begonnen, es zu nutzen. Wir sprechen von Telekommunikationskontrollsystemen, die weitaus mehr Zuverlässigkeit und Skalierbarkeit erfordern als ein typisches System, das für die Wall Street entwickelt wurde. Tatsächlich ist das Erlang-System nicht zuverlässig und erweiterbar, JavaScript hingegen schon. Erlang-Systeme sind einfach grundsolide.
Die Geschichte der Parallelität endet hier nicht. Auch wenn Ihr Programm Single-Threaded ist, kann ein funktionsfähiger Programm-Compiler es dennoch für die Ausführung auf mehreren CPUs optimieren. Bitte schauen Sie sich den folgenden Code an:
String s1 = SomethingLongOperation1();
String s2 = SomethingLongOperation2();
String s3 = concatenate(s1, s2);
In einer funktionalen Programmiersprache analysiert der Compiler den Code, um potenziell zeitaufwändige Funktionen zu identifizieren, die die Zeichenfolgen s1 und s2 erstellen, und führt sie dann parallel aus. Dies ist in imperativen Sprachen nicht möglich, in denen jede Funktion den Zustand außerhalb des Funktionsbereichs ändern kann und nachfolgende Funktionen von diesen Änderungen abhängen können. In funktionalen Sprachen ist die automatische Analyse von Funktionen und die Identifizierung geeigneter Kandidaten für die parallele Ausführung so einfach wie automatisches Funktions-Inlining! In diesem Sinne ist die Programmierung im funktionalen Stil „zukunftssicher“ (auch wenn ich Branchenbegriffe nicht gerne verwende, mache ich dieses Mal eine Ausnahme). Hardwarehersteller konnten CPUs nicht mehr schneller laufen lassen, also erhöhten sie die Geschwindigkeit der Prozessorkerne und erreichten durch Parallelität eine Vervierfachung der Geschwindigkeit. Natürlich haben sie auch vergessen zu erwähnen, dass das zusätzliche Geld, das wir ausgegeben haben, nur für Software zur Lösung paralleler Probleme verwendet wurde. Auf diesen Maschinen kann ein kleiner Anteil zwingender Software und 100 % funktionsfähige Software direkt parallel laufen.
Früher erforderte
die Hot-Bereitstellung von Code
die Installation von Updates unter Windows und ein Neustart des Computers war unvermeidlich, und das mehr als einmal, selbst wenn eine neue Version des Media Players installiert wurde.Windows XP hat diese Situation erheblich verbessert, aber sie ist immer noch nicht ideal (ich habe heute bei der Arbeit Windows Update ausgeführt und jetzt wird immer ein lästiges Symbol in der Taskleiste angezeigt, es sei denn, ich starte den Computer neu). Unix-Systeme laufen seit jeher in einem besseren Modus. Bei der Installation von Updates müssen nur systemrelevante Komponenten gestoppt werden, nicht das gesamte Betriebssystem. Dennoch ist dies für eine große Serveranwendung immer noch unbefriedigend. Telekommunikationssysteme müssen zu 100 % betriebsbereit sein, denn wenn die Notrufnummer während der Systemaktualisierung fehlschlägt, kann es zu Todesfällen kommen. Es gibt keinen Grund, warum Wall-Street-Firmen über das Wochenende ihre Dienste schließen müssen, um Updates zu installieren.
Im Idealfall wird der relevante Code aktualisiert, ohne dass irgendwelche Komponenten des Systems angehalten werden. Das ist in einer zwingenden Welt unmöglich. Bedenken Sie, dass alle Instanzen dieser Klasse nicht verfügbar sind, wenn die Laufzeit eine Java-Klasse hochlädt und eine neue Definition überschreibt, da ihr gespeicherter Zustand verloren geht. Wir könnten anfangen, einen mühsamen Versionskontrollcode zu schreiben, um dieses Problem zu lösen, dann alle Instanzen dieser Klasse serialisieren, diese Instanzen zerstören, diese Instanzen dann mit der neuen Definition dieser Klasse neu erstellen und dann die zuvor serialisierten Daten laden und hoffen, dass der Ladevorgang abgeschlossen ist Der Code portiert diese Daten ordnungsgemäß auf die neue Instanz. Darüber hinaus muss der Portierungscode für jedes Update manuell neu geschrieben werden und es muss sorgfältig darauf geachtet werden, dass die Beziehungen zwischen Objekten nicht unterbrochen werden. Die Theorie ist einfach, aber die Praxis ist nicht einfach.
Bei funktionalen Programmen werden alle Zustände, d. h. die an die Funktion übergebenen Parameter, auf dem Stapel gespeichert, was die Hot-Bereitstellung zum Kinderspiel macht! Tatsächlich müssen wir nur einen Unterschied zwischen dem Arbeitscode und der neuen Version machen und dann den neuen Code bereitstellen. Den Rest erledigt ein Sprachtool automatisch! Wenn Sie glauben, dass dies eine Science-Fiction-Geschichte ist, denken Sie noch einmal darüber nach. Seit Jahren aktualisieren Erlang-Ingenieure ihre laufenden Systeme, ohne sie zu unterbrechen.
Maschinengestütztes Denken und Optimieren
Eine interessante Eigenschaft funktionaler Sprachen ist, dass sie mathematisch begründet werden können. Da eine funktionale Sprache lediglich eine Implementierung eines formalen Systems ist, können alle auf dem Papier durchgeführten Operationen auf in dieser Sprache geschriebene Programme angewendet werden. Compiler können die mathematische Theorie nutzen, um einen Codeabschnitt in äquivalenten, aber effizienteren Code umzuwandeln [7]. Relationale Datenbanken werden seit Jahren dieser Art der Optimierung unterzogen. Es gibt keinen Grund, warum diese Technik nicht auf normale Software angewendet werden kann.
Darüber hinaus können Sie diese Techniken verwenden, um zu beweisen, dass Teile Ihres Programms korrekt sind, und vielleicht sogar Tools erstellen, um Ihren Code zu analysieren und automatisch Randfälle für Unit-Tests zu generieren! Diese Funktionalität hat für ein robustes System keinen Wert, aber wenn Sie einen Herzschrittmacher oder ein Flugsicherungssystem entwerfen, ist dieses Tool unverzichtbar. Wenn die von Ihnen geschriebenen Bewerbungen nicht zu den Kernaufgaben der Branche gehören, kann Ihnen ein solches Tool auch einen Trumpf gegenüber Ihren Mitbewerbern verschaffen.
23.4.3 Nachteile der funktionalen Programmierung
Nebenwirkungen von Abschlüssen
Bei der nicht strengen funktionalen Programmierung können Abschlüsse die externe Umgebung außer Kraft setzen (wir haben es bereits im vorherigen Kapitel gesehen), was Nebenwirkungen mit sich bringt, und wann solche Nebenwirkungen häufig auftreten Und wann Da sich die Umgebung, in der das Programm ausgeführt wird, häufig ändert, ist es schwierig, Fehler aufzuspüren.
//TODO:
rekursive Form
Obwohl Rekursion oft die prägnanteste Ausdrucksform ist, ist sie nicht so intuitiv wie nicht-rekursive Schleifen.
//TODO:
Die Schwäche des verzögerten Werts
//TODO: