Ich lese über funktionale Programmierung und habe festgestellt, dass Pattern Matching in vielen Artikeln als eines der Hauptmerkmale funktionaler Sprachen erwähnt wird.
Kann jemand einem Java / C ++ / JavaScript-Entwickler erklären, was das bedeutet?
Antworten:
Um den Mustervergleich zu verstehen, müssen drei Teile erklärt werden:
Algebraische Datentypen auf den Punkt gebracht
Mit ML-ähnlichen Funktionssprachen können Sie einfache Datentypen definieren, die als "disjunkte Vereinigungen" oder "algebraische Datentypen" bezeichnet werden. Diese Datenstrukturen sind einfache Container und können rekursiv definiert werden. Beispielsweise:
definiert eine stapelartige Datenstruktur. Betrachten Sie es als äquivalent zu diesem C #:
Die Bezeichner
Cons
undNil
definieren einfach eine einfache Klasse, wobei dieof x * y * z * ...
einen Konstruktor und einige Datentypen definiert. Die Parameter für den Konstruktor sind unbenannt und werden durch Position und Datentyp identifiziert.Sie erstellen Instanzen Ihrer
a list
Klasse als solche:Welches ist das gleiche wie:
Mustervergleich auf den Punkt gebracht
Pattern Matching ist eine Art Typprüfung. Nehmen wir also an, wir haben ein Stapelobjekt wie das oben beschriebene erstellt. Wir können Methoden implementieren, um den Stapel wie folgt zu betrachten und zu öffnen:
Die obigen Methoden entsprechen (obwohl nicht als solche implementiert) dem folgenden C #:
(Fast immer implementieren ML-Sprachen Pattern Matching ohne Laufzeit-Typentests oder Casts, daher täuscht der C # -Code etwas. Lassen Sie uns die Implementierungsdetails mit ein paar Handbewegungen beiseite schieben :))
Datenstrukturzerlegung auf den Punkt gebracht
Ok, kehren wir zur Peek-Methode zurück:
Der Trick besteht darin zu verstehen, dass die Bezeichner
hd
undtl
Bezeichner Variablen sind (ähm ... da sie unveränderlich sind, sind sie nicht wirklich "Variablen", sondern "Werte";)). Wenns
der Typ vorhanden istCons
, ziehen wir seine Werte aus dem Konstruktor heraus und binden sie an die Variablen mit dem Namenhd
undtl
.Der Mustervergleich ist nützlich, da wir damit eine Datenstruktur anhand ihrer Form anstelle ihres Inhalts zerlegen können . Stellen Sie sich also vor, wir definieren einen Binärbaum wie folgt:
Wir können einige Baumrotationen wie folgt definieren:
(Der
let rotateRight = function
Konstruktor ist Syntaxzucker fürlet rotateRight s = match s with ...
.)Wir können also nicht nur die Datenstruktur an Variablen binden, sondern auch einen Drilldown durchführen. Nehmen wir an, wir haben einen Knoten
let x = Node(Nil, 1, Nil)
. Wenn wir aufrufenrotateLeft x
, testen wir anhandx
des ersten Musters, das nicht übereinstimmt, weil das richtige Kind den TypNil
anstelle von hatNode
. Es wird zum nächsten Muster übergehen,x -> x
das mit jeder Eingabe übereinstimmt und unverändert zurückgegeben wird.Zum Vergleich würden wir die obigen Methoden in C # schreiben als:
Für ernst.
Pattern Matching ist fantastisch
Sie können mithilfe des Besuchermusters etwas Ähnliches wie den Mustervergleich in C # implementieren , es ist jedoch bei weitem nicht so flexibel, da Sie komplexe Datenstrukturen nicht effektiv zerlegen können. Wenn Sie den Mustervergleich verwenden, teilt Ihnen der Compiler außerdem mit, ob Sie einen Fall ausgelassen haben . Wie großartig ist das?
Überlegen Sie, wie Sie ähnliche Funktionen in C # oder Sprachen ohne Mustervergleich implementieren würden. Überlegen Sie, wie Sie es ohne Test-Tests und Casts zur Laufzeit machen würden. Es ist sicherlich nicht schwer , nur umständlich und sperrig. Und der Compiler überprüft nicht, ob Sie jeden Fall abgedeckt haben.
Der Pattern Matching hilft Ihnen also, Datenstrukturen in einer sehr praktischen, kompakten Syntax zu zerlegen und zu navigieren. So kann der Compiler die Logik Ihres Codes zumindest ein wenig überprüfen . Es ist wirklich ist ein Killer - Feature.
quelle
Kurze Antwort: Mustervergleich entsteht, weil funktionale Sprachen das Gleichheitszeichen als Behauptung der Äquivalenz statt als Zuweisung behandeln.
Lange Antwort: Der Mustervergleich ist eine Form des Versands, die auf der „Form“ des angegebenen Werts basiert. In einer funktionalen Sprache sind die von Ihnen definierten Datentypen normalerweise sogenannte diskriminierte Gewerkschaften oder algebraische Datentypen. Was ist zum Beispiel eine (verknüpfte) Liste? Eine verknüpfte Liste
List
von Dingen eines Typsa
ist entweder die leere ListeNil
oder ein Element des Typs, dasa
Cons
auf aList a
(eine Liste vona
s) geschrieben ist. In Haskell (der funktionalen Sprache, mit der ich am besten vertraut bin) schreiben wir diesAlle diskriminierten Gewerkschaften werden folgendermaßen definiert: Ein einzelner Typ hat eine feste Anzahl verschiedener Möglichkeiten, ihn zu schaffen; Die Schöpfer werden wie
Nil
undCons
hier Konstruktoren genannt. Dies bedeutet, dass ein Wert des TypsList a
mit zwei verschiedenen Konstruktoren erstellt werden konnte - er kann zwei verschiedene Formen haben. Angenommen, wir möchten einehead
Funktion schreiben , um das erste Element der Liste zu erhalten. In Haskell würden wir dies als schreibenDa
List a
es zwei verschiedene Arten von Werten geben kann, müssen wir jeden einzeln behandeln. Dies ist der Mustervergleich. Inhead x
, wennx
Streichhölzer das MusterNil
, dann führen wir den ersten Fall; Wenn es dem Muster entsprichtCons h _
, führen wir das zweite aus.Kurze Antwort, erklärt: Ich denke, eine der besten Möglichkeiten, über dieses Verhalten nachzudenken, besteht darin, Ihre Meinung zum Gleichheitszeichen zu ändern. In den Curly-Bracket-Sprachen bedeutet im Großen und Ganzen
=
Zuordnung:a = b
bedeutet "machena
inb
". In vielen funktionalen Sprachen bedeutet dies jedoch=
eine Behauptung der Gleichheit:let Cons a (Cons b Nil) = frob x
behauptet, dass das Ding auf der linken SeiteCons a (Cons b Nil)
dem Ding auf der rechten Seite äquivalent istfrob x
; Außerdem werden alle links verwendeten Variablen sichtbar. Dies passiert auch mit Funktionsargumenten: Wir behaupten, dass das erste Argument so aussiehtNil
, und wenn dies nicht der Fall ist, überprüfen wir es weiter.quelle
Cons
dasCons
ist die cons tructor , die eine (verknüpft) Liste aus einem Kopf aufbaut (dasa
) und ein Schwanz (derList a
). Der Name kommt von Lisp. In Haskell ist es für den integrierten Listentyp der:
Operator (der immer noch als "Nachteile" ausgesprochen wird).Es bedeutet, dass anstatt zu schreiben
Du kannst schreiben
Hey, C ++ unterstützt auch Pattern Matching.
quelle
Pattern Matching ähnelt einer überladenen Methode bei Steroiden. Der einfachste Fall wäre ungefähr der gleiche wie in Java. Argumente sind eine Liste von Typen mit Namen. Die richtige aufzurufende Methode basiert auf den übergebenen Argumenten und dient gleichzeitig als Zuordnung dieser Argumente zum Parameternamen.
Muster gehen nur einen Schritt weiter und können die übergebenen Argumente noch weiter zerstören. Es kann auch möglicherweise Wachen verwenden, um basierend auf dem Wert des Arguments tatsächlich eine Übereinstimmung zu erzielen. Zur Demonstration werde ich so tun, als hätte JavaScript einen Mustervergleich.
In foo2 erwartet es, dass a ein Array ist, bricht das zweite Argument auseinander, erwartet ein Objekt mit zwei Requisiten (prop1, prop2) und weist den Variablen d und e die Werte dieser Eigenschaften zu und erwartet dann das dritte Argument 35.
Anders als in JavaScript erlauben Sprachen mit Mustervergleich normalerweise mehrere Funktionen mit demselben Namen, aber unterschiedlichen Mustern. Auf diese Weise ist es wie eine Methodenüberladung. Ich werde ein Beispiel in erlang geben:
Verwischen Sie Ihre Augen ein wenig und Sie können sich dies in Javascript vorstellen. So etwas vielleicht:
Wenn Sie fibo aufrufen, basiert die verwendete Implementierung auf den Argumenten. Wenn Java jedoch auf Typen als einziges Mittel zum Überladen beschränkt ist, kann der Mustervergleich mehr bewirken.
Über die hier gezeigte Funktionsüberladung hinaus kann dasselbe Prinzip auch an anderen Stellen angewendet werden, z. B. bei Fallanweisungen oder bei der Destrukturierung von Assingments. JavaScript hat dies sogar in 1.7 .
quelle
Mit dem Mustervergleich können Sie einen Wert (oder ein Objekt) mit einigen Mustern abgleichen, um einen Zweig des Codes auszuwählen. Aus C ++ - Sicht klingt es möglicherweise etwas ähnlich wie die
switch
Anweisung. In funktionalen Sprachen kann der Mustervergleich zum Abgleichen von primitiven Standardwerten wie Ganzzahlen verwendet werden. Es ist jedoch nützlicher für zusammengesetzte Typen.Lassen Sie uns zunächst den Mustervergleich für primitive Werte demonstrieren (unter Verwendung von erweitertem Pseudo-C ++
switch
):Die zweite Verwendung befasst sich mit funktionalen Datentypen wie Tupeln (mit denen Sie mehrere Objekte in einem einzigen Wert speichern können) und diskriminierten Vereinigungen, mit denen Sie einen Typ erstellen können, der eine von mehreren Optionen enthalten kann. Das klingt ein bisschen so,
enum
außer dass jedes Etikett auch einige Werte enthalten kann. In einer Pseudo-C ++ - Syntax:Ein Wert vom Typ
Shape
kann jetzt entwederRectangle
mit allen Koordinaten oder aCircle
mit dem Mittelpunkt und dem Radius enthalten. Mit dem Mustervergleich können Sie eine Funktion für die Arbeit mit demShape
Typ schreiben :Schließlich können Sie auch verschachtelte Muster verwenden , die beide Funktionen kombinieren. Sie können beispielsweise verwenden
Circle(0, 0, radius)
, um alle Formen abzugleichen, deren Mittelpunkt im Punkt [0, 0] liegt und die einen beliebigen Radius haben (der Wert des Radius wird der neuen Variablen zugewiesenradius
).Dies mag aus C ++ - Sicht etwas ungewohnt klingen, aber ich hoffe, dass mein Pseudo-C ++ die Erklärung klar macht. Die funktionale Programmierung basiert auf ganz anderen Konzepten, daher ist sie in einer funktionalen Sprache sinnvoller!
quelle
Beim Mustervergleich wählt der Interpreter für Ihre Sprache eine bestimmte Funktion basierend auf der Struktur und dem Inhalt der von Ihnen angegebenen Argumente aus.
Es ist nicht nur eine funktionale Sprachfunktion, sondern auch für viele verschiedene Sprachen verfügbar.
Das erste Mal, dass ich auf die Idee stieß, war, als ich Prolog lernte, wo es wirklich zentral für die Sprache ist.
z.B
Der obige Code gibt das letzte Element einer Liste an. Das Eingabearg ist das erste und das Ergebnis das zweite.
Wenn die Liste nur ein Element enthält, wählt der Interpreter die erste Version aus und das zweite Argument wird auf das erste gesetzt, dh dem Ergebnis wird ein Wert zugewiesen.
Wenn die Liste sowohl einen Kopf als auch einen Schwanz hat, wählt der Dolmetscher die zweite Version aus und wiederholt sie, bis nur noch ein Element in der Liste vorhanden ist.
quelle
Für viele Menschen ist es einfacher, ein neues Konzept zu erlernen, wenn einige einfache Beispiele angegeben werden.
Angenommen, Sie haben eine Liste mit drei Ganzzahlen und möchten das erste und das dritte Element hinzufügen. Ohne Mustervergleich könnten Sie dies folgendermaßen tun (Beispiele in Haskell):
Obwohl dies ein Spielzeugbeispiel ist, stellen Sie sich vor, wir möchten die erste und dritte Ganzzahl an Variablen binden und diese summieren:
Diese Extraktion von Werten aus einer Datenstruktur ist das, was der Mustervergleich bewirkt. Sie "spiegeln" im Grunde die Struktur von etwas und geben Variablen an, die für die Orte von Interesse gebunden werden sollen:
Wenn Sie diese Funktion mit [1,2,3] als Argument aufrufen, wird [1,2,3] mit [first ,,
_
dritter] vereinheitlicht, wobei zuerst an 1, drittens an 3 gebunden und 2 verworfen wird (_
ist ein Platzhalter für Dinge, die dir egal sind).Wenn Sie Listen nur mit 2 als zweitem Element abgleichen möchten, können Sie dies folgendermaßen tun:
Dies funktioniert nur für Listen mit 2 als zweitem Element und löst ansonsten eine Ausnahme aus, da für nicht übereinstimmende Listen keine Definition für addFirstAndThird angegeben ist.
Bisher haben wir Pattern Matching nur zur Destrukturierung der Bindung verwendet. Darüber hinaus können Sie mehrere Definitionen derselben Funktion angeben, wobei die erste Übereinstimmungsdefinition verwendet wird. Daher ähnelt der Mustervergleich ein wenig einer "switch-Anweisung für Stereoide":
addFirstAndThird fügt gerne das erste und dritte Element von Listen mit 2 als zweitem Element hinzu, andernfalls "fallen durch" und "geben" 0 zurück. Diese "switch-like" -Funktionalität kann nicht nur in Funktionsdefinitionen verwendet werden, z.
Darüber hinaus ist es nicht auf Listen beschränkt, sondern kann auch mit anderen Typen verwendet werden, z. B. mit den Wert- und Nichts-Wertkonstruktoren des Typs "Vielleicht", um den Wert zu "entpacken":
Sicher, das waren nur Spielzeugbeispiele, und ich habe nicht einmal versucht, eine formale oder erschöpfende Erklärung zu geben, aber sie sollten ausreichen, um das Grundkonzept zu verstehen.
quelle
Sie sollten mit der Wikipedia-Seite beginnen , die eine ziemlich gute Erklärung gibt. Lesen Sie dann das entsprechende Kapitel des Haskell-Wikibooks .
Dies ist eine schöne Definition aus dem obigen Wikibook:
quelle
Hier ist ein wirklich kurzes Beispiel, das die Nützlichkeit des Mustervergleichs zeigt:
Angenommen, Sie möchten ein Element in einer Liste sortieren:
zu (Ich habe "New York" sortiert)
in einer zwingenderen Sprache würden Sie schreiben:
In einer funktionalen Sprache würden Sie stattdessen schreiben:
Wie Sie sehen können, weist die musterangepasste Lösung weniger Rauschen auf. Sie können deutlich erkennen, welche Fälle es gibt und wie einfach es ist, unsere Liste zu bereisen und zu destrukturieren.
Ich habe eine detailliertere Blog - Post über sie geschrieben hier .
quelle