Ich versuche, objektorientiertes Stil-Programmieren in Haskell zu verstehen, wissend, dass die Dinge aufgrund mangelnder Veränderlichkeit etwas anders sein werden. Ich habe mit Typklassen herumgespielt, aber mein Verständnis von ihnen beschränkt sich auf sie als Schnittstellen. Also habe ich ein C ++ - Beispiel programmiert, das ist der Standard-Diamant mit einer reinen Basis und virtueller Vererbung. Bat
erbt Flying
und Mammal
und beide Flying
und Mammal
erben Animal
.
Grundsätzlich interessiert mich, wie man eine solche Struktur in Haskell übersetzt, im Grunde würde das mir erlauben, eine Liste von Animal
s zu haben, so wie ich ein Array von (intelligenten) Zeigern auf Animal
s haben könnte C ++.
Das willst du einfach nicht tun, fang nicht mal an. OO hat sicherlich seine Vorzüge, aber "klassische Beispiele" wie Ihr C ++ sind fast immer erfundene Strukturen, die entworfen wurden, um das Paradigma in die Gehirne von Studenten zu hämmern, damit sie sich nicht darüber beschweren, wie dumm die Sprachen sind, die sie verwenden sollen < sup> † .
Die Idee scheint im Grunde genommen das Modellieren von "realen Objekten" durch Objekte in Ihrer Programmiersprache. Das kann ein guter Ansatz für tatsächliche Programmierprobleme sein, aber es macht nur Sinn, wenn Sie tatsächlich eine Analogie zwischen der Verwendung des realen Objekts und der Handhabung der OO-Objekte im Programm ziehen können.
Was für solche Tierbeispiele nur lächerlich ist. Wenn überhaupt, müssten die Methoden Dinge wie "Füttern", "Milch", "Schlachten" sein ... aber "Transport" ist eine irreführende Bezeichnung, ich würde das nehmen, um das Tier tatsächlich zu bewegen , die eher eine Methode der Umgebung sind, in der das Tier lebt, und im Grunde nur als Teil eines Besuchermusters Sinn macht.
describe
, type
und was Sie transport
nennen, sind dagegen viel einfacher. Dies sind im Wesentlichen typabhängige Konstanten oder einfache reine Funktionen. Nur OO Paranoia & amp; ddagger; ratifiziert sie Klassenmethoden zu machen.
Irgendetwas in der Art dieses Tierkrames, wo es im Grunde nur Daten gibt, wird viel einfacher, wenn du es nicht in etwas OO-ähnliches erzwingst, sondern einfach bei ihm bleibst (nützlich getippt) ) Daten in Haskell.
Da uns dieses Beispiel offensichtlich nicht weiter bringt, sollten wir uns etwas überlegen, wo OOP sinnvoll ist. Widget Toolkits kommen in den Sinn. Etwas wie
%Vor% Warum macht OO hier Sinn? Erstens erlaubt es uns ohne weiteres, eine heterogene Sammlung von Widgets zu speichern. Das ist in Haskell eigentlich nicht leicht zu erreichen, aber bevor Sie es versuchen, werden Sie sich vielleicht fragen, ob Sie es wirklich brauchen. Für bestimmte Container ist es vielleicht gar nicht so wünschenswert, dies zuzulassen. In Haskell ist parametrischer Polymorphismus sehr nützlich. Für jeden Widget-Typ beobachten wir, dass die Funktionalität von Container
sich ziemlich auf eine einfache Liste reduziert. Warum also nicht einfach eine Liste verwenden, wo immer Sie Container
brauchen?
Natürlich werden Sie in diesem Beispiel wahrscheinlich feststellen, dass Sie heterogene Container benötigen. Der direkteste Weg, sie zu erhalten, ist {-# LANGUAGE ExistentialQuantification #-}
:
In diesem Fall wäre Widget
eine Typklasse (könnte eine ziemlich wörtliche Übersetzung der abstrakten Klasse Widget
sein). In Haskell ist dies eher ein letzter Ausweg, aber vielleicht ist es genau hier.
Paned
ist eher eine Schnittstelle. Wir könnten hier eine andere Typklasse verwenden, die im Grunde die C ++ - Eins transkribiert:
ReEquipable
ist schwieriger, weil seine Methoden den Container tatsächlich mutieren. Das ist offensichtlich in Haskell problematisch. Aber auch hier ist es vielleicht nicht notwendig: Wenn Sie die Klasse Container
durch einfache Listen ersetzt haben, können Sie die Aktualisierungen möglicherweise als rein funktionale Aktualisierungen durchführen.
Wahrscheinlich wäre dies für die vorliegende Aufgabe jedoch zu ineffizient. Vollständig zu diskutieren, wie man veränderbare Aktualisierungen effizient durchführen kann, wäre zu viel für den Umfang dieser Antwort, aber solche Wege existieren z.B. mit lenses
.
OO übersetzt nicht so gut zu Haskell. Es gibt nicht einen einfachen generischen Isomorphismus, nur mehrere Annäherungen, unter denen man wählen muss, erfordern Erfahrung. So oft wie möglich sollten Sie vermeiden, sich dem Problem aus einem OO-Winkel zu nähern und stattdessen über Daten, Funktionen und Monad-Layer nachzudenken. Es stellt sich heraus, dass Sie in Haskell sehr weit kommen. Nur in wenigen Anwendungen ist OO so natürlich, dass es sich lohnt, es in die Sprache zu drücken.
† Sorry, dieses Thema bringt mich immer in den starken Meinungsmacher-Modus ...
& amp; ddagger; Diese Paranoia sind teilweise durch die Schwierigkeiten der Wandlungsfähigkeit motiviert, die in Haskell nicht auftreten.
In Haskell gibt es keine gute Methode, "Bäume" der Vererbung zu machen. Stattdessen machen wir normalerweise etwas wie
%Vor%Also verkapseln wir gemeinsame Informationen. Was in OOP nicht ungewöhnlich ist, "favorisieren Sie die Komposition über die Vererbung". Als nächstes erstellen wir diese Schnittstellen, sogenannte Klassen
%Vor% Dann würden wir Animal
, Mammal
und Bat
Instanzen von Named
machen, aber das war für jeden von ihnen sinnvoll.
Von nun an würden wir nur Funktionen in die entsprechende Kombination von Typklassen schreiben, es ist uns nicht wirklich wichtig, dass Bat
ein Animal
darin mit einem Namen vergraben hat. Wir sagen nur
und lassen Sie die zugrunde liegenden Typklassen sich darum kümmern, herauszufinden, wie mit den spezifischen Daten umzugehen ist. So können wir auf vielerlei Arten sichereren Code schreiben, zum Beispiel
%Vor% Mit foo
, wir haben keine Idee, was Subtyp von Top
zurückgegeben wird, müssen wir hässliche, laufzeitbasierte Casting tun, um es herauszufinden. Mit bar
garantieren wir statisch, dass wir bei unserer Schnittstelle bleiben, aber dass die zugrunde liegende Implementierung über die Funktion konsistent ist. Dies erleichtert das sichere Erstellen von Funktionen, die über verschiedene Schnittstellen hinweg für denselben Typ funktionieren.
TLDR; In Haskell setzen wir Daten kompositorischer zusammen und setzen dann auf eingeschränkten parametrischen Polymorphismus, um eine sichere Abstraktion über konkrete Typen hinweg zu gewährleisten, ohne Typinformationen zu verlieren.
Es gibt viele Möglichkeiten, dies in Haskell erfolgreich zu implementieren, aber nur wenige, die sich ähnlich wie Java "fühlen". Hier ein Beispiel: Wir modellieren jeden Typ unabhängig voneinander, bieten aber "Cast" -Operationen, die es uns ermöglichen, Subtypen von Animal
als Animal
Sie können dann ohne Probleme eine Liste von Animal
s erstellen. Manchmal implementieren Leute dies gerne über ExistentialTypes
und typeclasses
wodurch wir Flying
und Mammal
direkt in eine Liste einfügen können
aber das ist eigentlich nicht viel besser als das ursprüngliche Beispiel, da wir alle Informationen über jedes Element in der Liste weggeworfen haben, außer dass es eine IsAnimal
-Instanz hat, die, genau betrachtet, völlig gleichbedeutend mit der Aussage ist nur ein Animal
.
Wir könnten also genauso gut mit der ersten Lösung gegangen sein.
Viele andere Antworten weisen bereits darauf hin, wie Typklassen für Sie interessant sein könnten. Ich möchte jedoch darauf hinweisen, dass nach meiner Erfahrung viele Male, wenn Sie denken, dass eine Typenklasse die Lösung für ein Problem ist, es tatsächlich nicht ist. Ich glaube, das gilt besonders für Menschen mit einem OOP-Hintergrund.
Es gibt tatsächlich einen sehr beliebten Blog-Artikel zu diesem Haskell Antipattern: Existential Typeclass , Sie könnten es genießen!
Ein einfacherer Ansatz für Ihr Problem könnte darin bestehen, die Schnittstelle als einfachen algebraischen Datentyp zu modellieren, z. B.
%Vor% So dass Ihr bat
ein einfacher Wert wird:
Damit können Sie ein Programm definieren, das jedes Tier beschreibt, ähnlich wie Ihr Programm:
%Vor% Dies macht es einfach, eine Liste von Animal
-Werten zu haben und z.B. Drucken der Beschreibung jedes Tieres.
Tags und Links haskell c++ functional-programming oop virtual-method