Nachdem ich lange Zeit mit objektorientierter Programmierung gelernt und gearbeitet hatte, trat ich einen Schritt zurück, um über die Systemkomplexität nachzudenken.
"Complexity is anything that makes software hard to understand or to modify.
" - John OuterhoutBei einigen Recherchen fand ich funktionale Programmierkonzepte wie Unveränderlichkeit und reine Funktion. Diese Konzepte sind große Vorteile beim Aufbau von Funktionen ohne Nebenwirkungen, sodass die Wartung von Systemen einfacher ist - mit einigen anderen Vorteilen.
In diesem Beitrag werde ich Ihnen anhand vieler Codebeispiele mehr über die funktionale Programmierung und einige wichtige Konzepte erzählen.
In diesem Artikel wird Clojure als Beispiel für eine Programmiersprache verwendet, um die funktionale Programmierung zu erläutern. Wenn Sie mit einer LISP-Sprache nicht vertraut sind, habe ich denselben Beitrag auch in JavaScript veröffentlicht. Schauen Sie sich die funktionalen Programmierprinzipien in Javascript an
Was ist funktionale Programmierung?
Funktionale Programmierung ist ein Programmierparadigma - ein Stil zum Aufbau der Struktur und der Elemente von Computerprogrammen -, der Berechnungen als Bewertung mathematischer Funktionen behandelt und Zustandsänderungen und veränderbare Daten vermeidet - WikipediaReine Funktionen

Das erste grundlegende Konzept, das wir lernen, wenn wir funktionale Programmierung verstehen wollen, sind reine Funktionen . Aber was bedeutet das wirklich? Was macht eine Funktion rein?
Woher wissen wir also, ob eine Funktion vorhanden ist pure
oder nicht? Hier ist eine sehr strenge Definition der Reinheit:
- Es gibt das gleiche Ergebnis zurück, wenn die gleichen Argumente angegeben werden (es wird auch als bezeichnet
deterministic
). - Es verursacht keine beobachtbaren Nebenwirkungen
Es gibt das gleiche Ergebnis zurück, wenn die gleichen Argumente angegeben werden
Stellen Sie sich vor, wir möchten eine Funktion implementieren, die die Fläche eines Kreises berechnet. Eine unreine Funktion würde radius
als Parameter empfangen und dann berechnen radius * radius * PI
. In Clojure steht der Bediener an erster Stelle und radius * radius * PI
wird (* radius radius PI)
:
Warum ist das eine unreine Funktion? Einfach, weil es ein globales Objekt verwendet, das nicht als Parameter an die Funktion übergeben wurde.
Stellen Sie sich nun vor, einige Mathematiker argumentieren, dass der PI
Wert tatsächlich ist, 42
und ändern den Wert des globalen Objekts.
Unsere unreine Funktion ergibt nun 10 * 10 * 42
= 4200
. Für denselben Parameter ( radius = 10
) haben wir ein anderes Ergebnis. Lass es uns reparieren!
TA-DA ?! Jetzt übergeben wir immer den P- I
Wert als Parameter an die Funktion. Jetzt greifen wir nur noch auf Parameter zu, die an die Funktion übergeben wurden. Kein External object.
- Für die Parameter
radius = 10
&PI = 3.14
haben wir immer das gleiche Ergebnis:314.0
- Für die Parameter
radius = 10
&PI = 42
haben wir immer das gleiche Ergebnis:4200
Dateien lesen
Wenn unsere Funktion externe Dateien liest, ist dies keine reine Funktion - der Inhalt der Datei kann sich ändern.
Zufallszahlengenerierung
Jede Funktion, die auf einem Zufallszahlengenerator basiert, kann nicht rein sein.
Es verursacht keine beobachtbaren Nebenwirkungen
Beispiele für beobachtbare Nebenwirkungen sind das Ändern eines globalen Objekts oder eines als Referenz übergebenen Parameters.
Jetzt wollen wir eine Funktion implementieren, um einen ganzzahligen Wert zu empfangen und den um 1 erhöhten Wert zurückzugeben.
Wir haben den counter
Wert. Unsere unreine Funktion empfängt diesen Wert und weist den Zähler mit dem um 1 erhöhten Wert neu zu.
Beobachtung : Von der Veränderbarkeit wird bei der funktionalen Programmierung abgeraten.
Wir modifizieren das globale Objekt. Aber wie würden wir es schaffen pure
? Geben Sie einfach den um 1 erhöhten Wert zurück. So einfach ist das.
Sehen Sie, dass unsere reine Funktion increase-counter
2 zurückgibt, aber der counter
Wert immer noch der gleiche ist. Die Funktion gibt den inkrementierten Wert zurück, ohne den Wert der Variablen zu ändern.
Wenn wir diese beiden einfachen Regeln befolgen, wird es einfacher, unsere Programme zu verstehen. Jetzt ist jede Funktion isoliert und kann keine Auswirkungen auf andere Teile unseres Systems haben.
Reine Funktionen sind stabil, konsistent und vorhersehbar. Bei gleichen Parametern geben reine Funktionen immer das gleiche Ergebnis zurück. Wir müssen nicht an Situationen denken, in denen derselbe Parameter unterschiedliche Ergebnisse hat - weil dies niemals passieren wird.
Vorteile für reine Funktionen
Der Code ist definitiv einfacher zu testen. Wir müssen uns über nichts lustig machen. So können wir reine Funktionen mit unterschiedlichen Kontexten testen:
- Bei gegebenem Parameter
A
→ erwarten Sie, dass die Funktion einen Wert zurückgibtB
- Bei gegebenem Parameter
C
→ erwarten Sie, dass die Funktion einen Wert zurückgibtD
Ein einfaches Beispiel wäre eine Funktion zum Empfangen einer Sammlung von Zahlen und zum Erwarten, dass jedes Element dieser Sammlung erhöht wird.
Wir erhalten die numbers
Sammlung, verwenden sie map
mit der inc
Funktion, um jede Zahl zu erhöhen, und geben eine neue Liste mit inkrementierten Zahlen zurück.
Für die wäre input
[1 2 3 4 5]
das zu erwarten .output
[2 3 4 5 6]
Unveränderlichkeit
Im Laufe der Zeit unverändert oder nicht änderbar.
Wenn Daten unveränderlich sind, kann sich ihr Status nicht ändernnachdem es erstellt wurde. Wenn Sie ein unveränderliches Objekt ändern möchten, können Sie dies nicht. Stattdessen erstellen Sie ein neues Objekt mit dem neuen Wert.
In Javascript verwenden wir üblicherweise die for
Schleife. Diese nächste for
Anweisung enthält einige veränderbare Variablen.
Für jede Iteration ändern wir den i
und den sumOfValue
Status . Aber wie gehen wir mit Veränderlichkeit in der Iteration um? Rekursion! Zurück zu Clojure!
Hier haben wir also die sum
Funktion, die einen Vektor numerischer Werte empfängt. Das recur
springt zurück in die, loop
bis wir den Vektor leer bekommen (unsere Rekursion base case
). Für jede "Iteration" addieren wir den Wert zum total
Akkumulator.
Bei der Rekursion behalten wir unsere Variablen beiunveränderlich.
Beobachtung : Ja! Wir können reduce
diese Funktion implementieren. Wir werden dies im Higher Order Functions
Thema sehen.
Es ist auch sehr häufig den letzten aufzubauen Zustand eines Objekts. Stellen Sie sich vor, wir haben eine Zeichenfolge und möchten diese Zeichenfolge in eine Zeichenfolge umwandeln url slug
.
In OOP in Ruby würden wir beispielsweise eine Klasse erstellen UrlSlugify
. Und diese Klasse wird eine slugify!
Methode haben, um die Zeichenfolgeneingabe in eine umzuwandeln url slug
.
Wunderschönen! Es ist implementiert! Hier haben wir eine zwingende Programmierung, die genau sagt, was wir in jedem slugify
Prozess tun möchten - zuerst Kleinbuchstaben, dann unbrauchbare Leerzeichen entfernen und schließlich verbleibende Leerzeichen durch Bindestriche ersetzen.
Aber wir mutieren den Eingabestatus in diesem Prozess.
Wir können mit dieser Mutation umgehen, indem wir eine Funktionszusammensetzung oder Funktionsverkettung durchführen. Mit anderen Worten, das Ergebnis einer Funktion wird als Eingabe für die nächste Funktion verwendet, ohne die ursprüngliche Eingabezeichenfolge zu ändern.
Hier haben wir:
trim
: Entfernt Leerzeichen an beiden Enden einer Zeichenfolgelower-case
: konvertiert die Zeichenfolge in Kleinbuchstabenreplace
: Ersetzt alle Übereinstimmungsinstanzen durch Ersetzen in einer bestimmten Zeichenfolge
Wir kombinieren alle drei Funktionen und können "slugify"
unseren String.
Apropos Funktionen kombinieren , wir können die comp
Funktion verwenden, um alle drei Funktionen zusammenzusetzen. Lass uns einen Blick darauf werfen:
Referentielle Transparenz

Lassen Sie uns Folgendes implementieren square function
:
Diese (reine) Funktion hat bei gleichem Eingang immer den gleichen Ausgang.
Wenn Sie "2" als Parameter des square function
Testaments übergeben, wird immer 4 zurückgegeben. Jetzt können wir das (square 2)
durch 4 ersetzen. Das war's! Unsere Funktion ist referentially transparent
.
Wenn eine Funktion konsistent dasselbe Ergebnis für dieselbe Eingabe liefert, ist sie grundsätzlich referenziell transparent.
reine Funktionen + unveränderliche Daten = referenzielle Transparenz
Mit diesem Konzept können wir uns die Funktion auswendig lernen. Stellen Sie sich vor, wir haben diese Funktion:
Das ist (+ 5 8)
gleich 13
. Diese Funktion führt immer zu 13
. Also können wir das tun:
Und dieser Ausdruck wird immer dazu führen 16
. Wir können den gesamten Ausdruck durch eine numerische Konstante ersetzen und auswendig lernen.
Funktioniert als erstklassige Entitäten

Die Idee von Funktionen als erstklassige Entitäten ist, dass Funktionen auch als Werte behandelt und als Daten verwendet werden.
In Clojure ist es üblich defn
, Funktionen zu definieren, aber dies ist nur syntaktischer Zucker für (def foo (fn ...))
. fn
gibt die Funktion selbst zurück. defn
Gibt a zurück, var
das auf ein Funktionsobjekt zeigt.
Funktionen als erstklassige Entitäten können:
- beziehen sich darauf aus Konstanten und Variablen
- Übergeben Sie es als Parameter an andere Funktionen
- Geben Sie es als Ergebnis anderer Funktionen zurück
Die Idee ist, Funktionen als Werte zu behandeln und Funktionen wie Daten zu übergeben. Auf diese Weise können wir verschiedene Funktionen kombinieren, um neue Funktionen mit neuem Verhalten zu erstellen.
Stellen Sie sich vor, wir haben eine Funktion, die zwei Werte summiert und dann den Wert verdoppelt. Etwas wie das:
Nun eine Funktion, die Werte subtrahiert und das Doppelte zurückgibt:
Diese Funktionen haben eine ähnliche Logik, aber der Unterschied sind die Operatorfunktionen. Wenn wir Funktionen als Werte behandeln und diese als Argumente übergeben können, können wir eine Funktion erstellen, die die Operatorfunktion empfängt, und sie in unserer Funktion verwenden. Lass es uns bauen!
Erledigt! Jetzt haben wir ein f
Argument und verwenden es, um a
und zu verarbeiten b
. Wir haben die Funktionen +
und übergeben -
, um mit der Funktion zu komponieren double-operator
und ein neues Verhalten zu erstellen.
Funktionen höherer Ordnung
Wenn wir über Funktionen höherer Ordnung sprechen, meinen wir eine Funktion, die entweder:
- nimmt eine oder mehrere Funktionen als Argumente oder
- gibt eine Funktion als Ergebnis zurück
Die double-operator
oben implementierte Funktion ist eine Funktion höherer Ordnung, da sie eine Operatorfunktion als Argument verwendet.
Sie haben wahrscheinlich schon davon gehört filter
, map
und reduce
. Schauen wir uns diese an.
Filter
Bei einer bestimmten Sammlung möchten wir nach einem Attribut filtern. Die Filterfunktion erwartet, dass ein true
oder ein false
Wert bestimmt, ob das Element in die Ergebnissammlung aufgenommen werden soll oder nicht . Wenn der Rückrufausdruck lautet true
, wird das Element von der Filterfunktion in die Ergebnissammlung aufgenommen. Andernfalls wird es nicht.
Ein einfaches Beispiel ist, wenn wir eine Sammlung von ganzen Zahlen haben und nur die geraden Zahlen wollen.
Imperativer Ansatz
Ein zwingender Weg, dies mit Javascript zu tun, ist:
- Erstellen Sie einen leeren Vektor
evenNumbers
- iteriere über den
numbers
Vektor - Schieben Sie die geraden Zahlen auf den
evenNumbers
Vektor
Wir können die filter
Funktion höherer Ordnung verwenden, um die Funktion zu empfangen even?
und eine Liste mit geraden Zahlen zurückzugeben:
Ein interessantes Problem, das ich auf dem Hacker Rank FP Path gelöst habe, war das Filter Array-Problem . Die Problemidee besteht darin, ein bestimmtes Array von Ganzzahlen zu filtern und nur die Werte auszugeben, die kleiner als ein angegebener Wert sind X
.
Eine zwingende Javascript-Lösung für dieses Problem ist etwa:
Wir sagen genau, was unsere Funktion tun muss - durchlaufen Sie die Sammlung, vergleichen Sie das aktuelle Element der Sammlung mit x
und verschieben Sie dieses Element auf das, resultArray
wenn es die Bedingung erfüllt.
Deklarativer Ansatz
Wir wollen jedoch einen deklarativeren Weg, um dieses Problem zu lösen, und auch die filter
Funktion höherer Ordnung verwenden.
Eine deklarative Clojure-Lösung wäre ungefähr so:
Diese Syntax scheint in erster Linie etwas seltsam, ist aber leicht zu verstehen.
#(> x
%) ist nur eine anonyme Funktion, die e
s x empfängt und mit jedem Element in der Sammlung vergleicht n
. % repräsentiert den Parameter der anonymen Funktion - in diesem Fall das aktuelle Element innerhalb von t he fil
ter.
Wir können dies auch mit Karten tun. Stellen Sie sich vor, wir haben eine Karte von Menschen mit ihren name
und age
. Und wir möchten nur Personen über einen bestimmten Alterswert filtern, in diesem Beispiel Personen, die älter als 21 Jahre sind.
Zusammenfassung des Codes:
- Wir haben eine Liste von Personen (mit
name
undage
). - wir haben die anonyme Funktion
#(< 21 (:age
%)). Denken Sie daran, dass th
e% das aktuelle Element aus der Sammlung darstellt? Nun, das Element der Sammlung ist eine Personenkarte. Wenn wirdo (:age {:name "TK" :age 2
6}), gibt ese,
in diesem Fall den Alterswert 26 zurück. - Wir filtern alle Personen basierend auf dieser anonymen Funktion.
Karte
Die Idee der Karte ist es, eine Sammlung zu transformieren.
Diemap
Methode transformiert eine Sammlung, indem sie eine Funktion auf alle ihre Elemente anwendet und aus den zurückgegebenen Werten eine neue Sammlung erstellt.Lassen Sie uns die gleiche people
Sammlung oben bekommen. Wir möchten jetzt nicht nach "über dem Alter" filtern. Wir wollen nur eine Liste von Strings, so etwas wie TK is 26 years old
. Die letzte Zeichenfolge kann also sein, :name is :age years old
wo :name
und :age
sind Attribute von jedem Element in der people
Sammlung.
In einer zwingenden Javascript-Weise wäre es:
In einer deklarativen Clojure-Weise wäre es:
Die ganze Idee ist, eine bestimmte Sammlung in eine neue Sammlung umzuwandeln.
Ein weiteres interessantes Problem mit dem Hacker-Rang war das Problem mit der Aktualisierungsliste . Wir möchten nur die Werte einer bestimmten Sammlung mit ihren absoluten Werten aktualisieren.
Zum Beispiel muss der Eingang [1 2 3 -4 5]
der Ausgang sein [1 2 3 4 5]
. Der absolute Wert von -4
ist 4
.
Eine einfache Lösung wäre eine direkte Aktualisierung für jeden Erfassungswert.
Wir verwenden die Math.abs
Funktion, um den Wert in seinen absoluten Wert umzuwandeln und die direkte Aktualisierung durchzuführen.
Dies ist keine funktionale Möglichkeit, diese Lösung zu implementieren.
Zuerst haben wir etwas über Unveränderlichkeit gelernt. Wir wissen, wie wichtig Unveränderlichkeit ist, um unsere Funktionen konsistenter und vorhersehbarer zu machen. Die Idee ist, eine neue Sammlung mit allen absoluten Werten aufzubauen.
Zweitens, warum nicht map
hier verwenden, um alle Daten zu "transformieren"?
Meine erste Idee war, eine to-absolute
Funktion zu erstellen , die nur einen Wert verarbeitet.
Wenn es negativ ist, wollen wir es in einen positiven Wert (den absoluten Wert) umwandeln. Andernfalls müssen wir es nicht transformieren.
Nachdem wir nun wissen, wie man absolute
für einen Wert vorgeht, können wir diese Funktion verwenden, um sie als Argument an die map
Funktion zu übergeben. Erinnern Sie sich, dass a higher order function
eine Funktion als Argument empfangen und verwenden kann? Ja, Karte kann es!
Beeindruckend. So schön! ?
Reduzieren
Die Idee des Reduzierens besteht darin, eine Funktion und eine Sammlung zu erhalten und einen Wert zurückzugeben, der durch Kombinieren der Elemente erstellt wurde.
Ein häufiges Beispiel, über das gesprochen wird, ist der Gesamtbetrag einer Bestellung. Stellen Sie sich vor, Sie wären auf einer Einkaufswebsite. Sie haben hinzugefügt Product 1
, Product 2
, Product 3
, und Product 4
in Ihren Warenkorb (Bestellung). Nun wollen wir den Gesamtbetrag des Warenkorbs berechnen.
In zwingender Weise würden wir die Bestellliste durchlaufen und jeden Produktbetrag zum Gesamtbetrag addieren.
Mit reduce
können wir eine Funktion erstellen, die das behandelt, amount sum
und sie als Argument an die reduce
Funktion übergeben.
Hier haben wir shopping-cart
die Funktion sum-amount
, die den Strom empfängt total-amount
, und das current-product
Objekt für sum
sie.
Die get-total-amount
Funktion wird verwendet , um reduce
die shopping-cart
durch die Verwendung sum-amount
und ab 0
.
Eine andere Möglichkeit, den Gesamtbetrag zu erhalten, besteht darin, map
und zu komponieren reduce
. Was meine ich damit? Wir können das verwenden, map
um das shopping-cart
in eine Sammlung von amount
Werten umzuwandeln , und dann einfach die reduce
Funktion mit +
Funktion verwenden.
Der get-amount
empfängt das Produktobjekt und gibt nur den amount
Wert zurück. Was wir hier haben, ist [10 30 20 60]
. Und dann reduce
kombiniert das alle Elemente durch Addition. Wunderschönen!
Wir haben uns angesehen, wie jede Funktion höherer Ordnung funktioniert. Ich möchte Ihnen ein Beispiel zeigen, wie wir alle drei Funktionen in einem einfachen Beispiel zusammensetzen können.
Apropos shopping cart
, stellen wir diese Liste der Produkte in unserem Auftrag haben:
Wir möchten die Gesamtmenge aller Bücher in unserem Warenkorb. So einfach ist das. Der Algorithmus?
- Filtern nach Buchtyp
- Verwandeln Sie den Einkaufswagen mithilfe einer Karte in eine Betragssammlung
- Kombinieren Sie alle Elemente, indem Sie sie mit Reduzieren addieren
Erledigt! ?
Ressourcen
Ich habe einige Ressourcen organisiert, die ich gelesen und studiert habe. Ich teile diejenigen, die ich wirklich interessant fand. Weitere Ressourcen finden Sie in meinem Github-Repository für funktionale Programmierung .
- Ruby-spezifische Ressourcen
- Javascript-spezifische Ressourcen
- Clojure spezifische Ressourcen
Intros
- FP in JS lernen
- Einführung in FP mit Python
- Übersicht über FP
- Eine kurze Einführung in das funktionale JS
- Was ist FP?
- Jargon für funktionale Programmierung
Reine Funktionen
- Was ist eine reine Funktion?
- Reine funktionale Programmierung 1
- Reine funktionale Programmierung 2
Unveränderliche Daten
- Unveränderlicher DS für die funktionale Programmierung
- Warum ein gemeinsamer veränderlicher Zustand die Wurzel allen Übels ist
- Strukturelles Teilen in Clojure: Teil 1
- Strukturelles Teilen in Clojure: Teil 2
- Strukturelles Teilen in Clojure: Teil 3
- Strukturelles Teilen in Clojure: Letzter Teil
Funktionen höherer Ordnung
- Eloquent JS: Funktionen höherer Ordnung
- Spaß Spaß Funktion Filter
- Spaß Spaß Funktion Karte
- Fun Fun Funktion Basic Reduce
- Fun Fun Funktion Advanced Reduce
- Clojure Funktionen höherer Ordnung
- Reiner Funktionsfilter
- Rein funktionale Karte
- Rein funktionale Reduzierung
Deklarative Programmierung
- Deklarative Programmierung vs Imperativ
Das ist es!
Hey Leute, ich hoffe du hattest Spaß beim Lesen dieses Beitrags und ich hoffe du hast hier viel gelernt! Dies war mein Versuch zu teilen, was ich lerne.
Hier ist das Repository mit allen Codes aus diesem Artikel.
Komm lerne mit mir. Ich teile Ressourcen und meinen Code in diesem Learning Functional Programming-Repository .
Ich hoffe, Sie haben hier etwas Nützliches gesehen. Und bis zum nächsten Mal! :) :)
Mein Twitter & Github. ☺
TK.