Warum sind diskriminierende Gewerkschaften mit funktionaler Programmierung verbunden?


30

In vielen Jahren der OO-Programmierung habe ich verstanden, was diskriminierte Gewerkschaften sind, aber ich habe sie nie wirklich vermisst. Ich habe vor kurzem einige funktionale Programmierung in C # gemacht und jetzt stelle ich fest, dass ich mir immer wünsche, ich hätte sie. Das verwirrt mich, weil das Konzept der diskriminierten Gewerkschaften auf den ersten Blick völlig unabhängig von der funktionalen Dichotomie zu sein scheint.

Enthält die funktionale Programmierung etwas, das diskriminierte Gewerkschaften nützlicher macht als dies in OO der Fall wäre, oder ist es so, dass ich mich dazu gezwungen habe, das Problem "besser" zu analysieren, meine Standards einfach erhöht habe und nun ein besseres verlange Modell?


Der Ausdruck problem en.wikipedia.org/wiki/Expression_problem könnte relevant sein
xji

Da es sich nicht wirklich um eine richtige Antwort auf die Frage handelt, werde ich stattdessen als Kommentar antworten, aber die Ceylon- Programmiersprache hat Unionstypen und sie scheinen sich in andere OO- / gemischte Paradigmasprachen einzuschleichen - mir fallen TypeScript und Scala ein. Auch Java-Sprachenzahlen können als eine Art Implementierung von diskriminierten Gewerkschaften verwendet werden.
Roland Tepp

Antworten:


45

Diskriminierte Gewerkschaften glänzen wirklich in Verbindung mit der Mustererkennung, bei der Sie je nach Fall ein anderes Verhalten auswählen. Dieses Muster widerspricht jedoch grundsätzlich den reinen OO-Prinzipien.

In reinen OO sollten Verhaltensunterschiede durch die Typen (Objekte) selbst definiert und eingekapselt werden. Die Entsprechung zum Pattern Matching besteht also darin, eine einzelne Methode für das Objekt selbst aufzurufen, die dann von den fraglichen Subtypen überladen wird, um ein anderes Verhalten zu definieren. Das Untersuchen des Objekttyps von außen (wie bei der Mustererkennung) wird als Gegenmuster angesehen.

Der grundlegende Unterschied besteht darin, dass Daten und Verhalten bei der funktionalen Programmierung voneinander getrennt sind, während Daten und Verhalten in OO zusammengefasst sind.

Dies ist der historische Grund. Eine Sprache wie C # entwickelt sich von einer klassischen OO-Sprache zu einer Multi-Paradigma-Sprache, indem immer mehr Funktionsmerkmale integriert werden.


6
Ein diskriminierter Vereinigungs- / Summentyp ist nicht wie ein Vererbungsbaum oder mehrere Klassen, die eine Schnittstelle implementieren. Es ist ein Typ mit vielen Arten von Werten. Sie verhindern auch nicht die Einkapselung; Der Client muss nicht wissen, dass Sie mehrere Arten von Werten haben. Betrachten Sie eine verknüpfte Liste, die entweder ein leerer Knoten oder ein Wert und ein Verweis auf einen anderen Knoten sein kann. Ohne Summentypen hacken Sie ohnehin eine diskriminierte Union, indem Sie Variablen für den Wert und die Referenz haben, aber ein Objekt mit einer Nullreferenz als leeren Knoten behandeln und so tun, als ob die Wertvariable nicht existiert.
Doval

2
Im Allgemeinen schließen Sie die Variablen für alle möglichen Arten von Werten in eine Klasse ein, plus eine Art Flag oder eine Aufzählung, die Ihnen anzeigt, um welche Art von Wert es sich bei dieser Instanz handelt, und tanzen um die Variablen herum, die dem nicht entsprechen nett.
Doval

7
@Doval Es gibt eine "Standard" -Codierung eines algebraischen Datentyps in Klassen, indem eine Interface- / abstrakte Basisklasse den Typ insgesamt darstellt und für jeden Fall des Typs eine Unterklasse vorhanden ist. Um den Mustervergleich zu handhaben, haben Sie eine Methode, die für jeden Fall eine Funktion übernimmt, also für eine Liste, die Sie auf der Oberfläche der obersten Ebene haben würden, List<A>die Methode B Match<B>(B nil, Func<A,List<A>,B> cons). Dies ist beispielsweise genau das Muster, das Smalltalk für Boolesche Werte verwendet. So geht Scala im Grunde auch damit um. Die Verwendung mehrerer Klassen ist ein Implementierungsdetail, das nicht offengelegt werden muss.
Derek Elkins

3
@Doval: Das explizite Überprüfen auf Null-Referenzen wird ebenfalls nicht als idiomatisch angesehen.
JacquesB

@JacquesB Die Nullprüfung ist ein Implementierungsdetail. Für den Client der verknüpften Liste gibt es normalerweise eine isEmptyMethode, die prüft, ob der Verweis auf den nächsten Knoten null ist.
Doval

35

Nachdem ich vor dem Erlernen der funktionalen Programmierung in Pascal und Ada programmiert habe, verbinde ich diskriminierte Gewerkschaften nicht mit funktionaler Programmierung.

Diskriminierte Gewerkschaften sind in gewisser Weise das doppelte Erbe. Die erste Option ermöglicht das einfache Hinzufügen von Vorgängen zu einer festen Gruppe von Typen (denjenigen in der Union), und die Vererbung ermöglicht das einfache Hinzufügen von Typen mit einer festen Gruppe von Vorgängen. (Das einfache Hinzufügen von beiden wird als Ausdrucksproblem bezeichnet. Dies ist ein besonders schwieriges Problem für Sprachen mit einem statischen Typsystem.)

Aufgrund der Betonung von OO auf Typen und der doppelten Betonung der funktionalen Programmierung auf Funktionen weisen funktionale Programmiersprachen eine natürliche Affinität zu Unionstypen auf und bieten syntaktische Strukturen, um deren Verwendung zu vereinfachen.


7

Imperative Programmiertechniken, wie sie in OO häufig verwendet werden, basieren häufig auf zwei Mustern:

  1. Erfolg oder Ausnahme werfen,
  2. Zurück null, um "kein Wert" oder Fehler anzuzeigen.

Das Funktionsparadigma vermeidet normalerweise beides und gibt vorzugsweise einen zusammengesetzten Typ zurück, der den Grund für Erfolg / Misserfolg oder den Wert / keinen Wert angibt.

Für diese zusammengesetzten Typen sind diskriminierte Gewerkschaften genau das Richtige. In der ersten Instanz könnten Sie beispielsweise trueeine Datenstruktur zurückgeben, die den Fehler beschreibt. Im zweiten Fall, eine Vereinigung , die einen Wert enthält, oder none, niletc. Der zweite Fall ist so verbreitet, dass viele funktionale Sprachen haben ein „vielleicht“ oder „Option“ Typ eingebaute in diesen Wert / keine Vereinigung zu vertreten.

Wenn Sie beispielsweise mit C # zu einem funktionalen Stil wechseln, werden Sie schnell feststellen, dass diese zusammengesetzten Typen erforderlich sind. void/throwund nullfühle mich einfach nicht richtig mit solchem ​​Code. Und diskriminierte Gewerkschaften (DUs) sind genau das Richtige. Sie wollten sie, genau wie viele von uns.

Die gute Nachricht ist, dass es viele Bibliotheken gibt, die DUs in z. B. C # modellieren (schauen Sie sich zum Beispiel meine eigene Succinc <T> -Bibliothek an).


2

Summentypen wären in den gängigen OO-Sprachen im Allgemeinen weniger nützlich, da sie eine ähnliche Art von Problem lösen wie die OO-Subtypisierung. Eine Möglichkeit, sie zu betrachten, besteht darin, dass beide Subtypen behandeln, aber OO ist, dass man openeinem übergeordneten Typ beliebige Subtypen hinzufügen kann und dass Summentypen sind, closeddh man bestimmt im Voraus, welche Subtypen gültig sind.

Jetzt kombinieren viele OO-Sprachen die Untertypisierung mit anderen Konzepten wie geerbten Strukturen, Polymorphismus, Referenztypisierung usw., um sie allgemein nützlicher zu machen. Eine Konsequenz ist, dass sie in der Regel aufwändiger einzurichten sind (mit Klassen und Konstruktoren und so weiter) und daher eher nicht für Dinge wie Results und Options usw. verwendet werden, bis die allgemeine Typisierung üblich wurde.

Ich würde auch sagen, dass der Fokus auf reale Beziehungen, den die meisten Menschen zu Beginn der OO-Programmierung erlernten, z. Obwohl die Ideen ziemlich ähnlich sind.

Ein möglicher Grund dafür, warum funktionale Sprachen das geschlossene Schreiben dem offenen Schreiben vorziehen, ist, dass sie den Mustervergleich bevorzugen. Dies ist nützlich für den Funktionspolymorphismus, funktioniert aber auch sehr gut bei geschlossenen Typen, da der Compiler statisch überprüfen kann, ob der Abgleich alle Subtypen abdeckt. Dies kann dazu führen, dass sich die Sprache konsistenter anfühlt, obwohl ich nicht glaube, dass es einen inhärenten Vorteil gibt (ich könnte mich irren).


Übrigens: Scala hat die Vererbung geschlossen. sealedbedeutet "kann nur in derselben Kompilierungseinheit erweitert werden", wodurch Sie den Satz von Unterklassen zur Entwurfszeit schließen können.
Jörg W Mittag

-3

Swift verwendet gerne diskriminierende Gewerkschaften, außer es nennt sie "Enums". Enums sind eine der fünf grundlegenden Kategorien von Objekten in Swift, nämlich class, struct, enum, tuple und closure. Swift-Optionen sind Aufzählungen, bei denen es sich um diskriminierende Gewerkschaften handelt, und sie sind für jeden Swift-Code unbedingt erforderlich.

Die Prämisse, dass "diskriminierende Gewerkschaften mit funktionaler Programmierung verbunden sind", ist also falsch.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.