Die Schlüsselwörter data
und type
verwirren mich immer.Was ist der Unterschied zwischen den Keywords "data" und "type"?
Ich möchte wissen, was ist der Unterschied zwischen data
und type
und wie man sie verwendet.
Die Schlüsselwörter data
und type
verwirren mich immer.Was ist der Unterschied zwischen den Keywords "data" und "type"?
Ich möchte wissen, was ist der Unterschied zwischen data
und type
und wie man sie verwendet.
type
erklärt Synonym. Ein Typ-Synonym ist ein neuer Name für einen vorhandenen Typ. Zum Beispiel ist dies, wie String
in the standard library definiert:
type String = [Char]
String
ein anderer Name für eine Liste von Char
s ist. GHC wird alle Verwendungen von String
in Ihrem Programm mit [Char]
zur Kompilierzeit ersetzen.
Um klar zu sein, ein String
ist buchstäblich eine Liste von Char
s. Es ist nur ein Alias. Sie können alle Standard-Listenfunktionen auf String
Werte verwenden:
-- length :: [a] -> Int
ghci> length "haskell"
7
-- reverse :: [a] -> [a]
ghci> reverse "functional"
"lanoitcnuf"
data
eine Art neue Daten erklärt, die im Gegensatz zu einer Art Synonym, unterscheidet sich von jedem anderen Typ ist. Datentypen haben eine Anzahl von Konstruktoren definieren die möglichen Fälle Ihres Typs. Zum Beispiel ist dies, wie Bool
in the standard library definiert:
data Bool = False | True
A Bool
Wert entweder True
oder False
sein kann. Die Datentypen unterstützen Mustererkennung, sodass Sie eine Laufzeit-Fallanalyse für einen Wert eines Datentyps durchführen können.
yesno :: Bool -> String
yesno True = "yes"
yesno False = "no"
data
Typen mehrere Konstruktoren (wie bei Bool
) haben können, können durch andere Arten parametriert werden, können andere Typen in ihnen enthalten können, und sich selbst rekursiv beziehen. Hier ist ein Modell von Ausnahmen, das dies zeigt; Ein Error a
enthält eine Fehlermeldung des Typs a
und möglicherweise den Fehler, der es verursacht hat.
data Error a = Error { value :: a, cause :: Maybe Error }
type ErrorWithMessage = Error String
myError1, myError2 :: ErrorWithMessage
myError1 = Error "woops" Nothing
myError2 = Error "myError1 was thrown" (Just myError1)
Es ist wichtig zu erkennen, dass data
eine neue Art erklärt, die voneinander im System von einem anderen Typ ist.Wenn String
als data
Typ mit eine Liste von Char
s deklariert wurde (anstelle eines Synonyms), könnten Sie keine Listenfunktionen darauf verwenden.
data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString -- type error
Es gibt eine weitere Vielzahl von Typdeklaration: newtype
. Dies funktioniert eher wie eine data
Deklaration - es führt einen neuen Datentyp ein, der von jedem anderen Typ getrennt ist, und kann pattern-matched sein - außer dass Sie auf einen einzelnen Konstruktor mit einem einzelnen Feld beschränkt sind. Mit anderen Worten, ein newtype
ist ein data
Typ, der einen bestehenden Typ umschließt.
Der wesentliche Unterschied ist die Kosten ein newtype
: der Compiler verspricht, dass ein newtype
in der gleichen Art und Weise wie die Art dargestellt wird, es umschließt. Es gibt keine Laufzeitkosten für das Packen oder Entpacken von newtype
. Dies macht newtype
s nützlich für die Herstellung administrative (anstatt strukturelle) Unterschiede zwischen den Werten.
newtype
s interagieren gut mit Typklassen. Betrachten Sie zum Beispiel Monoid
, die Klasse von Typen mit einer Möglichkeit, Elemente (mappend
) und ein spezielles "leeres" Element (mempty
) zu kombinieren. Int
kann in eine Monoid
in vielerlei Hinsicht gemacht werden, einschließlich der Addition mit 0 und Multiplikation mit 1. Wie können wir wählen, welche für eine mögliche Monoid
Instanz von Int
zu verwenden? Es ist besser, keine Präferenz anzugeben und newtype
s zu verwenden, um entweder die Verwendung ohne Laufzeitkosten zu aktivieren. Paraphrasiert the standard library:
-- introduce a type Sum with a constructor Sum which wraps an Int, and an extractor getSum which gives you back the Int
newtype Sum = Sum { getSum :: Int }
instance Monoid Sum where
(Sum x) `mappend` (Sum y) = Sum (x + y)
mempty = Sum 0
newtype Product = Product { getProduct :: Int }
instance Monoid Product where
(Product x) `mappend` (Product y) = Product (x * y)
mempty = Product 1
Mit data
Sie neuen Datentyp erstellen und einen Konstruktor für sie erklären:
data NewData = NewDataConstructor
Mit type
definieren Sie einfach einen Alias:
type MyChar = Char
Im type
Fall, dass Sie Wert passieren kann MyChar
Typ zu Funktion erwartet eine Char
und umgekehrt, aber Sie können dies für data MyChar = MyChar Char
nicht tun.
type
funktioniert wie let
: ermöglicht es Ihnen, einen wiederverwendbaren Namen etwas zu geben, aber dass etwas funktioniert immer so, als ob Sie die Definition inlined hatten. So
type ℝ = Double
f :: ℝ -> ℝ -> ℝ
f x y = let x2 = x^2
in x2 + y
verhält sich genauso wie
f' :: Double -> Double -> Double
f' x y = x^2 + y
wie in: Sie überall in Ihrem Code f
mit f'
und umgekehrt ersetzen kann; nichts würde sich ändern.
OTOH, beide data
und newtype
erzeugen eine undurchsichtige Abstraktion. Sie sind eher wie ein Klassenkonstruktor in OO: Obwohl ein Wert einfach in Bezug auf eine einzige Nummer implementiert ist, verhält es sich nicht notwendigerweise verhalten wie eine solche Zahl. Zum Beispiel
newtype Logscaledℝ = LogScaledℝ { getLogscaled :: Double }
instance Num LogScaledℝ where
LogScaledℝ a + LogScaledℝ b = LogScaledℝ $ a*b
LogScaledℝ a - LogScaledℝ b = LogScaledℝ $ a/b
LogScaledℝ a * LogScaledℝ b = LogScaledℝ $ a**b
Obwohl hier Logscaledℝ
ist datenweise immer noch nur eine Double
Zahl verhält es sich deutlich von Double
.