Ich lerne Haskell von learnyouahaskell.com . Ich habe Probleme, Typkonstruktoren und Datenkonstruktoren zu verstehen. Zum Beispiel verstehe ich den Unterschied nicht wirklich:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
und das:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Ich verstehe, dass der erste einfach einen Konstruktor ( Car
) verwendet, um Daten vom Typ zu erstellen Car
. Ich verstehe den zweiten nicht wirklich.
Wie werden Datentypen wie folgt definiert:
data Color = Blue | Green | Red
in all das passen?
Von dem, was ich verstehe, das dritte Beispiel ( Color
ist) ein Typ, der in drei Zuständen sein kann: Blue
, Green
oder Red
. Dies steht jedoch im Widerspruch zu meinem Verständnis der ersten beiden Beispiele: Car
Kann sich der Typ nur in einem Zustand befinden, Car
für dessen Erstellung verschiedene Parameter erforderlich sind? Wenn ja, wie passt das zweite Beispiel dazu?
Im Wesentlichen suche ich nach einer Erklärung, die die obigen drei Codebeispiele / Konstrukte vereinheitlicht.
Car
es sich sowohl um einen Typkonstruktor (auf der linken Seite=
) als auch um einen Datenkonstruktor (auf der rechten Seite) handelt. Im ersten BeispielCar
akzeptiert der Typkonstruktor keine Argumente, im zweiten Beispiel drei. In beiden Beispielen verwendet derCar
Datenkonstruktor drei Argumente (die Typen dieser Argumente sind jedoch in einem Fall festgelegt und in dem anderen parametrisiert).Car :: String -> String -> Int -> Car
), um Daten vom Typ zu erstellenCar
. Der zweite verwendet einfach einen Datenkonstruktor (Car :: a -> b -> c -> Car a b c
), um Daten vom Typ zu erstellenCar a b c
.Antworten:
In einer
data
Erklärung, ein Typkonstruktor ist das , was auf der linken Seite des Gleichheitszeichens. Die Datenkonstruktoren sind die Dinge auf der rechten Seite des Gleichheitszeichens. Sie verwenden Typkonstruktoren, bei denen ein Typ erwartet wird, und Sie verwenden Datenkonstruktoren, bei denen ein Wert erwartet wird.Datenkonstruktoren
Zur Vereinfachung können wir mit einem Beispiel eines Typs beginnen, der eine Farbe darstellt.
Hier haben wir drei Datenkonstruktoren.
Colour
ist ein Typ undGreen
ein Konstruktor, der einen Wert vom Typ enthältColour
. EbensoRed
undBlue
sind beide Konstruktoren, die Werte vom Typ konstruierenColour
. Wir könnten uns vorstellen, es aufzupeppen!Wir haben nach wie vor nur die Art
Colour
, aberRGB
ist kein Wert - es ist eine Funktion drei Ints nehmen und Rückkehr Wert!RGB
hat den TypRGB
ist ein Datenkonstruktor, bei dem es sich um eine Funktion handelt, die einige Werte als Argumente verwendet und diese dann verwendet, um einen neuen Wert zu erstellen . Wenn Sie objektorientiert programmiert haben, sollten Sie dies erkennen. In OOP nehmen Konstruktoren auch einige Werte als Argumente und geben einen neuen Wert zurück!In diesem Fall erhalten
RGB
wir einen Farbwert , wenn wir auf drei Werte anwenden !Wir haben einen Wert vom Typ
Colour
konstruiert, indem wir den Datenkonstruktor angewendet haben. Ein Datenkonstruktor enthält entweder einen Wert wie eine Variable oder verwendet andere Werte als Argument und erstellt einen neuen Wert . Wenn Sie zuvor programmiert haben, sollte Ihnen dieses Konzept nicht sehr fremd sein.Pause
Wenn Sie einen Binärbaum zum Speichern von
String
s erstellen möchten , können Sie sich vorstellen, so etwas zu tunWas wir hier sehen, ist ein Typ
SBTree
, der zwei Datenkonstruktoren enthält. Mit anderen Worten, es gibt zwei Funktionen (nämlichLeaf
undBranch
), die Werte desSBTree
Typs konstruieren . Wenn Sie nicht mit der Funktionsweise von Binärbäumen vertraut sind, bleiben Sie einfach dran. Sie müssen eigentlich nicht wissen, wie Binärbäume funktionieren, nur dass dieserString
s auf irgendeine Weise speichert .Wir sehen auch, dass beide Datenkonstruktoren ein
String
Argument annehmen - dies ist der String, den sie im Baum speichern werden.Aber! Was wäre, wenn wir auch speichern könnten, müssten
Bool
wir einen neuen Binärbaum erstellen. Es könnte ungefähr so aussehen:Typkonstruktoren
Beide
SBTree
undBBTree
sind Typkonstruktoren. Aber es gibt ein eklatantes Problem. Sehen Sie, wie ähnlich sie sind? Das ist ein Zeichen dafür, dass Sie wirklich irgendwo einen Parameter wollen.Also können wir das tun:
Jetzt führen wir eine Typvariable
a
als Parameter in den Typkonstruktor ein. In dieser ErklärungBTree
ist eine Funktion geworden. Es nimmt einen Typ als Argument und gibt einen neuen Typ zurück .Wenn wir
Bool
beispielsweise als Argument an übergebenBTree
, wird der Typ zurückgegebenBTree Bool
, bei dem es sich um einen Binärbaum handelt, in demBool
s gespeichert ist . Ersetzen Sie jedes Vorkommen der Typvariablena
durch den TypBool
, und Sie können selbst sehen, wie es wahr ist.Wenn Sie möchten, können Sie
BTree
als Funktion mit der Art anzeigenArten sind etwas wie Typen - das
*
zeigt einen konkreten Typ an, also sagen wir, esBTree
handelt sich von einem konkreten Typ zu einem konkreten Typ.Einpacken
Treten Sie einen Moment zurück und beachten Sie die Ähnlichkeiten.
Ein Datenkonstruktor ist eine "Funktion", die 0 oder mehr Werte annimmt und Ihnen einen neuen Wert zurückgibt.
Ein Typkonstruktor ist eine "Funktion", die 0 oder mehr Typen akzeptiert und Ihnen einen neuen Typ zurückgibt.
Datenkonstruktoren mit Parametern sind cool, wenn wir geringfügige Abweichungen in unseren Werten wünschen. Wir setzen diese Abweichungen in den Parametern ein und lassen den Typ, der den Wert erstellt, entscheiden, welche Argumente sie eingeben. Im gleichen Sinne sind Typkonstruktoren mit Parametern cool wenn wir leichte Abweichungen in unseren Typen wollen! Wir setzen diese Variationen als Parameter und lassen den Typ, der den Typ erstellt, entscheiden, welche Argumente er eingeben wird.
Eine Fallstudie
Als Heimstrecke hier können wir den
Maybe a
Typ berücksichtigen . Seine Definition istHier
Maybe
ist ein Typkonstruktor, der einen konkreten Typ zurückgibt.Just
ist ein Datenkonstruktor, der einen Wert zurückgibt.Nothing
ist ein Datenkonstruktor, der einen Wert enthält. Wenn wir uns die Art von ansehenJust
, sehen wir dasMit anderen Worten,
Just
nimmt einen Wert vom Typ ana
und gibt einen Wert vom Typ zurückMaybe a
. Wenn wir uns die Art von ansehenMaybe
, sehen wir dasMit anderen Worten,
Maybe
nimmt einen konkreten Typ und gibt einen konkreten Typ zurück.Noch einmal! Der Unterschied zwischen einer konkreten Typ- und einer Typkonstruktorfunktion. Sie können keine Liste von
Maybe
s erstellen - wenn Sie versuchen, auszuführenSie erhalten eine Fehlermeldung. Sie können jedoch eine Liste von
Maybe Int
oder erstellenMaybe a
. Das liegt daran, dassMaybe
es sich um eine Typkonstruktorfunktion handelt, eine Liste jedoch Werte eines konkreten Typs enthalten muss.Maybe Int
undMaybe a
sind konkrete Typen (oder, wenn Sie möchten, Aufrufe, Typkonstruktorfunktionen einzugeben, die konkrete Typen zurückgeben.)quelle
data Colour = Red | Green | Blue
"Wir haben überhaupt keine Konstruktoren" einfach falsch ist. Typkonstruktoren und Datenkonstruktoren müssen keine Argumente annehmen, siehe z. B. haskell.org/haskellwiki/Constructor, in dem darauf hingewiesen wird, dass indata Tree a = Tip | Node a (Tree a) (Tree a)
"Es gibt zwei Datenkonstruktoren, Tip und Node".-XEmptyDataDecls
), mit der Sie dies tun können. Da es, wie Sie sagen, keine Werte für diesen Typ gibt, kann eine Funktionf :: Int -> Z
beispielsweise niemals zurückkehren (denn was würde sie zurückgeben?). Sie können jedoch nützlich sein, wenn Sie Typen möchten, sich aber nicht wirklich um Werte kümmern .:k Z
und es gab mir nur einen Stern.Haskell hat algebraische Datentypen , die nur sehr wenige andere Sprachen haben. Das verwirrt dich vielleicht.
In anderen Sprachen können Sie normalerweise einen "Datensatz", eine "Struktur" oder ähnliches erstellen, der eine Reihe benannter Felder enthält, die verschiedene Datentypen enthalten. Sie können manchmal auch eine „Aufzählung“, machen die einen (kleinen) Satz von festen möglichen Werten hat (zB Ihr
Red
,Green
undBlue
).In Haskell können Sie beide gleichzeitig kombinieren . Seltsam, aber wahr!
Warum heißt es "algebraisch"? Nun, die Nerds sprechen von "Summentypen" und "Produkttypen". Beispielsweise:
Ein
Eg1
Wert ist im Grunde entweder eine Ganzzahl oder eine Zeichenfolge. Die Menge aller möglichenEg1
Werte ist also die "Summe" der Menge aller möglichen ganzzahligen Werte und aller möglichen Zeichenkettenwerte. Nerds werden daherEg1
als "Summentyp" bezeichnet. Andererseits:Jeder
Eg2
Wert besteht sowohl aus einer Ganzzahl als auch aus einer Zeichenfolge. Die Menge aller möglichenEg2
Werte ist also das kartesische Produkt der Menge aller ganzen Zahlen und der Menge aller Zeichenfolgen. Die beiden Sätze werden miteinander "multipliziert", dies ist also ein "Produkttyp".Die algebraischen Typen von Haskell sind Summentypen von Produkttypen . Sie geben einem Konstruktor mehrere Felder, um einen Produkttyp zu erstellen, und Sie haben mehrere Konstruktoren, um eine Summe (von Produkten) zu erstellen.
Angenommen, Sie haben Daten, die Daten entweder als XML oder als JSON ausgeben, und es wird ein Konfigurationsdatensatz benötigt. Die Konfigurationseinstellungen für XML und JSON sind jedoch völlig unterschiedlich. Sie könnten also so etwas tun:
(Natürlich mit einigen geeigneten Feldern.) Solche Dinge können Sie in normalen Programmiersprachen nicht machen, weshalb die meisten Leute nicht daran gewöhnt sind.
quelle
union
s, mit einer Tag-Disziplin. :)union
, sehen mich die Leute an wie "Wer zum Teufel benutzt das jemals ?" ;-)union
in meiner C-Karriere viel verwendet gesehen . Bitte lassen Sie es nicht unnötig klingen, da dies nicht der Fall ist.Beginnen Sie mit dem einfachsten Fall:
Dies definiert einen „Typkonstruktor“ ,
Color
die keine Argumente haben - und es hat drei „Datenbauer“,Blue
,Green
undRed
. Keiner der Datenkonstruktoren akzeptiert Argumente. Dies bedeutet , dass es drei vom TypColor
:Blue
,Green
undRed
.Ein Datenkonstruktor wird verwendet, wenn Sie einen Wert erstellen müssen. Mögen:
Erstellt einen Wert
myFavoriteColor
mit demGreen
Datenkonstruktor - undmyFavoriteColor
ist vom Typ,Color
da dies der vom Datenkonstruktor erzeugte Wertetyp ist.Ein Typkonstruktor wird verwendet, wenn Sie einen Typ erstellen müssen . Dies ist normalerweise beim Schreiben von Unterschriften der Fall:
In diesem Fall rufen Sie den
Color
Typkonstruktor auf (der keine Argumente akzeptiert).Immer noch bei mir?
Stellen Sie sich nun vor, Sie wollten nicht nur Rot / Grün / Blau-Werte erstellen, sondern auch eine "Intensität" angeben. Ein Wert zwischen 0 und 256. Sie können dies tun, indem Sie jedem Datenkonstruktor ein Argument hinzufügen, sodass Sie am Ende Folgendes erhalten:
Jetzt nimmt jeder der drei Datenkonstruktoren ein Argument vom Typ
Int
. Der Typkonstruktor (Color
) akzeptiert immer noch keine Argumente. Da meine Lieblingsfarbe ein dunkles Grün ist, könnte ich schreibenUnd wieder nennt es das
Green
Datenkonstruktor auf und ich erhalte einen Wert vom TypColor
.Stellen Sie sich vor, Sie möchten nicht vorschreiben, wie Menschen die Intensität einer Farbe ausdrücken. Einige möchten vielleicht einen numerischen Wert, wie wir es gerade getan haben. Andere mögen in Ordnung sein, wenn nur ein Boolescher Wert "hell" oder "nicht so hell" anzeigt. Die Lösung hierfür besteht darin, nicht fest zu codieren
Int
die Datenkonstruktoren , sondern eine Typvariable zu verwenden:Jetzt verwendet unser Typkonstruktor ein Argument (einen anderen Typ, den wir gerade aufrufen
a
!), Und alle Datenkonstruktoren verwenden ein Argument (einen Wert!) Dieses Typsa
. Also könntest du habenoder
Beachten Sie, wie wir den
Color
Typkonstruktor mit einem Argument (einem anderen Typ) aufrufen , um den "effektiven" Typ zu erhalten, der von den Datenkonstruktoren zurückgegeben wird. Dies berührt das Konzept der Arten die Sie bei einer oder zwei Tassen Kaffee lesen möchten.Nun haben wir herausgefunden, was Datenkonstruktoren und Typkonstruktoren sind und wie Datenkonstruktoren andere Werte als Argumente und Typkonstruktoren andere Typen als Argumente verwenden können. HTH.
quelle
->
in der Signatur.a
indata Color a = Red a
.a
ist ein Platzhalter für einen beliebigen Typ. Sie können dasselbe jedoch in einfachen Funktionen haben, z. B. nimmt eine Funktion vom Typ(a, b) -> a
ein Tupel von zwei Werten (von Typena
undb
) und liefert den ersten Wert. Es ist eine "generische" Funktion, da sie nicht den Typ der Tupelelemente vorgibt - sie gibt nur an, dass die Funktion einen Wert des gleichen Typs wie das erste Tupelelement liefert.Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.
Das ist sehr hilfreich.Wie andere betonten, ist Polymorphismus hier nicht so schrecklich nützlich. Schauen wir uns ein anderes Beispiel an, mit dem Sie wahrscheinlich bereits vertraut sind:
Dieser Typ verfügt über zwei Datenkonstruktoren.
Nothing
ist etwas langweilig, es enthält keine nützlichen Daten. Auf der anderen SeiteJust
enthält einen Wert vona
- welcher Typa
auch immer haben mag. Schreiben wir eine Funktion, die diesen Typ verwendet, z. B. den Kopf einerInt
Liste zu ermitteln, falls vorhanden (ich hoffe, Sie stimmen zu, dass dies nützlicher ist, als einen Fehler auszulösen):Also in diesem Fall
a
ist einInt
, aber es würde auch für jeden anderen Typ funktionieren. Tatsächlich können Sie unsere Funktion für jede Art von Liste verwenden (auch ohne die Implementierung zu ändern):Auf der anderen Seite können Sie Funktionen schreiben, die nur einen bestimmten Typ von
Maybe
zKurz gesagt, mit Polymorphismus geben Sie Ihrem eigenen Typ die Flexibilität, mit Werten verschiedener anderer Typen zu arbeiten.
In Ihrem Beispiel können Sie irgendwann entscheiden, dass dies
String
nicht ausreicht, um das Unternehmen zu identifizieren, aber es muss einen eigenen Typ habenCompany
(der zusätzliche Daten wie Land, Adresse, Rückkonten usw. enthält). Ihre erste Implementierung vonCar
müsste geändert werden, um sieCompany
anstelleString
ihres ersten Werts zu verwenden. Ihre zweite Implementierung ist in Ordnung, Sie verwenden sie wie zuvorCar Company String Int
und sie würde wie zuvor funktionieren (natürlich müssen Funktionen, die auf Unternehmensdaten zugreifen, geändert werden).quelle
data Color = Blue ; data Bright = Color
? Ich habe es in ghci versucht, und es scheint, dass die Farbe im Typkonstruktor nichts mit dem Farbdatenkonstruktor in der Bright-Definition zu tun hat. Es gibt nur zwei Farbkonstruktoren, einer für Daten und der andere für Typ.data
odernewtype
(z. B.data Bright = Bright Color
) "umbrechen" oder verwendentype
, um ein Synonym zu definieren (ztype Bright = Color
. B. ).Der zweite enthält den Begriff "Polymorphismus".
Das
a b c
kann von jedem Typ sein. Zum Beispiela
kann ein sein[String]
,b
kann sein[Int]
undc
kann sein[Char]
.Während der erste Typ festgelegt ist: Unternehmen ist ein
String
, Modell ist einString
und Jahr istInt
.Das Auto-Beispiel zeigt möglicherweise nicht die Bedeutung der Verwendung von Polymorphismus. Stellen Sie sich jedoch vor, Ihre Daten sind vom Listentyp. Eine Liste kann enthalten
String, Char, Int ...
In diesen Situationen benötigen Sie die zweite Möglichkeit, Ihre Daten zu definieren.Was den dritten Weg betrifft, denke ich nicht, dass er in den vorherigen Typ passen muss. Dies ist nur eine andere Möglichkeit, Daten in Haskell zu definieren.
Dies ist meine bescheidene Meinung als Anfänger.
Übrigens: Stellen Sie sicher, dass Sie Ihr Gehirn gut trainieren und sich dabei wohl fühlen. Es ist der Schlüssel, um Monad später zu verstehen.
quelle
Es geht um Typen : Im ersten Fall legen Sie die Typen
String
(für Unternehmen und Modell) undInt
für das Jahr fest. Im zweiten Fall sind Sie allgemeiner.a
,,b
undc
können die gleichen Typen wie im ersten Beispiel sein oder etwas völlig anderes. Beispielsweise kann es nützlich sein, das Jahr als Zeichenfolge anstelle einer Ganzzahl anzugeben. Und wenn Sie möchten, können Sie sogar IhrenColor
Typ verwenden.quelle