Es scheint, als würde F # -Code häufig Muster mit Typen vergleichen. Bestimmt
match opt with
| Some val -> Something(val)
| None -> Different()
scheint üblich zu sein.
Aus OOP-Sicht ähnelt das einem Kontrollfluss auf der Grundlage einer Laufzeit-Typprüfung, die normalerweise verpönt wäre. Um es auszudrücken, würden Sie in OOP wahrscheinlich lieber eine Überladung verwenden:
type T =
abstract member Route : unit -> unit
type Foo() =
interface T with
member this.Route() = printfn "Go left"
type Bar() =
interface T with
member this.Route() = printfn "Go right"
Das ist sicherlich mehr Code. OTOH, meines Erachtens haben strukturelle Vorteile:
- die erweiterung auf eine neue form von
T
ist einfach; - Ich brauche mir keine Sorgen zu machen, ob der Kontrollfluss für die Routenwahl doppelt vorhanden ist. und
- Die Wahl der Route ist in dem Sinne unveränderlich, dass ich
Foo
mich nie mehr umBar.Route()
die Implementierung kümmern muss , wenn ich eine in der Hand habe
Gibt es Vorteile beim Mustervergleich gegenüber Typen, die ich nicht sehe? Wird es als idiomatisch angesehen oder ist es eine Fähigkeit, die nicht allgemein verwendet wird?
object-oriented
functional-programming
f#
pattern-matching
Larry OBrien
quelle
quelle
But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.
klingt zu dogmatisch. Manchmal möchten Sie Ihre Ops von Ihrer Hierarchie trennen: 1) Sie können einer Hierarchie keine Ops hinzufügen, b / c Sie besitzen die Hierarchie nicht. 2) Die Klassen, für die Sie die Operation durchführen möchten, stimmen nicht mit Ihrer Hierarchie überein. 3) Sie könnten die Option zu Ihrer Hierarchie hinzufügen, möchten aber nicht, dass die API Ihrer Hierarchie mit ein paar Drecksachen überfrachtet wird, die die meisten Clients nicht verwenden.Some
undNone
sind keine Typen. Sie sind beide Konstruktoren, deren Typenforall a. a -> option a
undforall a. option a
(leider nicht sicher, wie die Syntax für Typanmerkungen in F # lautet).Antworten:
Sie haben Recht, dass OOP-Klassenhierarchien sehr eng mit diskriminierten Gewerkschaften in F # verwandt sind und dass der Mustervergleich sehr eng mit dynamischen Typentests verwandt ist. Tatsächlich kompiliert F # auf diese Weise diskriminierte Gewerkschaften nach .NET!
In Bezug auf die Erweiterbarkeit gibt es zwei Seiten des Problems:
Das heißt, F # gibt Ihnen eine Warnung, wenn Sie einen Fall beim Pattern Matching verpassen, so dass das Hinzufügen neuer Verbindungsfälle eigentlich nicht so schlimm ist.
In Bezug auf das Finden von Duplikaten in der Stammauswahl gibt F # eine Warnung aus, wenn Sie eine Übereinstimmung haben, die doppelt vorhanden ist, z. B .:
Die Tatsache, dass die Routenwahl unveränderlich ist, könnte ebenfalls problematisch sein. Wenn Sie zum Beispiel die Implementierung einer Funktion zwischen
Foo
undBar
Fällen teilen möchten, aber etwas anderes für denZoo
Fall tun möchten , können Sie dies einfach mithilfe des Mustervergleichs codieren:Im Allgemeinen konzentriert sich FP eher darauf, zuerst die Typen zu entwerfen und dann Funktionen hinzuzufügen. Es profitiert also wirklich von der Tatsache, dass Sie Ihre Typen (Domain-Modell) in ein paar Zeilen in einer einzigen Datei unterbringen und dann einfach die Funktionen hinzufügen können, die für das Domain-Modell gelten.
Die beiden Ansätze - OO und FP - ergänzen sich sehr gut und haben Vor- und Nachteile. Das Knifflige (aus der OO-Perspektive) ist, dass F # normalerweise den FP-Stil als Standard verwendet. Wenn jedoch wirklich mehr Unterklassen hinzugefügt werden müssen, können Sie immer Schnittstellen verwenden. Aber in den meisten Systemen müssen Sie gleichermaßen Typen und Funktionen hinzufügen, sodass die Auswahl wirklich keine Rolle spielt - und die Verwendung diskriminierter Gewerkschaften in F # ist besser.
Ich würde diese großartige Blogserie für weitere Informationen empfehlen .
quelle
Sie haben richtig beobachtet, dass der Mustervergleich (im Wesentlichen eine aufgeladene switch-Anweisung) und der dynamische Versand Ähnlichkeiten aufweisen. Sie koexistieren auch in einigen Sprachen, mit einem sehr erfreulichen Ergebnis. Es gibt jedoch leichte Unterschiede.
Ich könnte das Typensystem verwenden, um einen Typ zu definieren, der nur eine feste Anzahl von Untertypen haben kann:
Es wird niemals einen anderen Subtyp von
Bool
oder gebenOption
, daher erscheint eine Subklassifizierung nicht sinnvoll (einige Sprachen wie Scala haben einen Subklassifizierungsbegriff, der dies handhabt - eine Klasse kann außerhalb der aktuellen Kompilierungseinheit als „endgültig“ markiert werden, Subtypen jedoch kann innerhalb dieser Zusammenstellungseinheit definiert werden).Da die Untertypen eines Typs wie
Option
jetzt statisch bekannt sind , kann der Compiler warnen, wenn wir vergessen, einen Fall in unserer Musterübereinstimmung zu behandeln. Dies bedeutet, dass eine Musterübereinstimmung eher einem speziellen Downcast ähnelt, der uns zwingt, alle Optionen zu handhaben.Darüber hinaus impliziert der dynamische Methodenversand (der für OOP erforderlich ist) auch eine Laufzeittypprüfung, jedoch auf eine andere Art. Es ist daher ziemlich irrelevant, ob wir diese Typprüfung explizit durch eine Musterübereinstimmung oder implizit durch einen Methodenaufruf durchführen.
quelle
Der F # -Musterabgleich erfolgt in der Regel mit einer diskriminierten Vereinigung und nicht mit Klassen (und ist daher technisch überhaupt keine Typprüfung). Auf diese Weise kann der Compiler Sie warnen, wenn Sie Fälle in einer Musterübereinstimmung nicht berücksichtigt haben.
Beachten Sie außerdem, dass Sie in einem funktionalen Stil die Dinge eher nach Funktionen als nach Daten organisieren. Mit Hilfe von Musterübereinstimmungen können Sie die verschiedenen Funktionen an einem Ort zusammenfassen, anstatt sie über Klassen zu verteilen. Dies hat auch den Vorteil, dass Sie sehen können, wie andere Fälle direkt neben dem Ort behandelt werden, an dem Sie Ihre Änderungen vornehmen müssen.
Das Hinzufügen einer neuen Option sieht dann so aus:
quelle
Teilweise sehen Sie es häufiger in der funktionalen Programmierung, weil Sie Typen verwenden, um häufiger Entscheidungen zu treffen. Mir ist klar, dass Sie wahrscheinlich nur zufällig ausgewählte Beispiele ausgewählt haben, aber das OOP-Äquivalent zu Ihrem Mustervergleichsbeispiel würde häufiger so aussehen:
Mit anderen Worten, es ist relativ selten, Polymorphismus zu verwenden, um Routineprobleme wie Nullprüfungen in OOP zu vermeiden. So wie ein OO-Programmierer nicht in jeder Situation ein Null-Objekt erzeugt , überlastet ein funktionaler Programmierer nicht immer eine Funktion, insbesondere wenn Sie wissen, dass Ihre Musterliste garantiert vollständig ist. Wenn Sie das Typsystem in mehreren Situationen verwenden, werden Sie feststellen, dass es so verwendet wird, wie Sie es nicht gewohnt sind.
Im Gegensatz dazu würde die idiomatische funktionale Programmierung entspricht Ihr OOP Beispiel höchstwahrscheinlich nicht Pattern - Matching verwenden, sondern müßte
fooRoute
undbarRoute
Funktionen , die als Argumente an dem anrufenden Code übergeben bekommen würden. Wenn jemand in dieser Situation Pattern Matching verwendet, wird dies normalerweise als falsch angesehen, genauso wie das Einschalten von Typen in OOP als falsch angesehen wird.Wann wird Pattern Matching als guter funktionaler Programmcode angesehen? Wenn Sie mehr als nur die Typen betrachten und die Anforderungen erweitern, müssen Sie keine weiteren Fälle hinzufügen. Zum Beispiel
Some val
wird nicht nur überprüft, obopt
der Typ vorhanden istSome
, sondern es wird auch eine Bindungval
an den zugrunde liegenden Typ hergestellt, um eine typsichere Verwendung auf der anderen Seite von zu ermöglichen->
. Sie wissen, dass Sie höchstwahrscheinlich nie einen dritten Fall benötigen werden, also ist es eine gute Verwendung.Der Mustervergleich ähnelt möglicherweise oberflächlich einer objektorientierten switch-Anweisung, es ist jedoch noch viel mehr im Gange, insbesondere bei längeren oder verschachtelten Mustern. Stellen Sie sicher, dass Sie alles berücksichtigen, was es tut, bevor Sie es für einen schlecht gestalteten OOP-Code erklären. Häufig wird eine Situation, die in einer Vererbungshierarchie nicht sauber dargestellt werden kann, kurz und bündig behandelt.
quelle
Some
undNone
sind nicht Typen, so dass Sie auf Typen nicht Musterabgleich sind. Sie Muster-Match auf Konstrukteuren des gleichen Typs . Das ist nicht so, als würde man "instanceof" fragen.