Haskell Language
Klassen eingeben
Suche…
Einführung
Typenklassen in Haskell sind ein Mittel, um das mit einem Typ verknüpfte Verhalten getrennt von der Definition dieses Typs zu definieren. Während Sie beispielsweise in Java das Verhalten als Teil der Definition des Typs definieren - dh in einer Schnittstelle, einer abstrakten Klasse oder einer konkreten Klasse -, hält Haskell diese beiden Dinge getrennt.
Es gibt eine Reihe von typeclasses bereits definiert in Haskell base
Die Beziehung zwischen ihnen ist im Abschnitt "Anmerkungen" unten dargestellt.
Bemerkungen
Das folgende Diagramm aus dem Artikel Typeclassopedia zeigt die Beziehung zwischen den verschiedenen Typenklassen in Haskell.
Vielleicht und die Functor Class
In Haskell können Datentypen genauso wie Funktionen Argumente haben. Nehmen Sie zum Beispiel den Typ Maybe
.
Maybe
ist ein sehr nützlicher Typ, der es erlaubt, die Idee des Versagens oder die Möglichkeit davon darzustellen. Mit anderen Worten, wenn die Möglichkeit besteht, dass eine Berechnung fehlschlägt, verwenden wir dort den Maybe
Typ. Maybe
wirkt es wie ein Wrapper für andere Typen und gibt ihnen zusätzliche Funktionen.
Ihre tatsächliche Erklärung ist ziemlich einfach.
Maybe a = Just a | Nothing
Was dies sagt, ist, dass ein Maybe
in zwei Formen kommt, ein Just
, das den Erfolg darstellt, und ein Nothing
, das einen Misserfolg darstellt. Just
nur ein Argument, das den Typ des Maybe
, und Nothing
nimmt nichts. Beispielsweise hat der Wert Just "foo"
den Typ " Maybe String
Just "foo"
handelt es sich um einen Zeichenfolgentyp, der mit der zusätzlichen Just "foo"
-Funktion Maybe
wird. Der Wert Nothing
hat einen Typ. Maybe a
wo a
beliebiger Typ sein kann.
Diese Idee des Umwickelns von Typen, um ihnen zusätzliche Funktionalität zu verleihen, ist sehr nützlich und kann auf mehr als nur Maybe
. Weitere Beispiele sind die Typen Either
, IO
und List, die jeweils unterschiedliche Funktionen bieten. Es gibt jedoch einige Aktionen und Fähigkeiten, die allen diesen Wrapper-Typen gemeinsam sind. Das bemerkenswerteste davon ist die Fähigkeit, den eingekapselten Wert zu ändern.
Es ist üblich, sich diese Arten von Typen als Kästchen vorzustellen, in die Werte eingefügt werden können. Verschiedene Kästchen enthalten unterschiedliche Werte und machen unterschiedliche Dinge, aber keine ist nützlich, ohne auf den Inhalt darin zugreifen zu können.
Um diese Idee zu kapseln, enthält Haskell eine Standard-Typenklasse namens Functor
. Es ist wie folgt definiert.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Wie man sieht, hat die Klasse eine einzige Funktion, fmap
, aus zwei Argumenten. Das erste Argument ist eine Funktion von einem Typ a
zu einem anderen b
. Das zweite Argument ist ein Funktor (Wrapper-Typ), der einen Wert vom Typ a
. Es gibt einen Funktionscode (Wrapper-Typ) zurück, der einen Wert vom Typ b
.
Einfach ausgedrückt nimmt fmap
eine Funktion an und bezieht sich auf den Wert innerhalb eines Funktors. Dies ist die einzige Funktion, die erforderlich ist, damit ein Typ Mitglied der Functor
Klasse ist. Functor
ist jedoch äußerst nützlich. Funktionen, die auf Funktoren mit spezifischeren Anwendungen ausgeführt werden, finden Sie in den Typenklassen Applicative
und Monad
.
Typ Klassenvererbung: Ord Typ Klasse
Haskell unterstützt einen Begriff der Klassenerweiterung. Die Klasse Ord
erbt beispielsweise alle Vorgänge in Eq
, verfügt jedoch zusätzlich über eine compare
Funktion, die eine Ordering
zwischen Werten zurückgibt. Ord
kann auch die üblichen Vergleichsoperatoren für die Reihenfolge sowie eine min
Methode und eine max
Methode enthalten.
Die Schreibweise =>
hat dieselbe Bedeutung wie in einer Funktionssignatur und erfordert die Implementierung von Eq
Typ a
, um Ord
.
data Ordering = EQ | LT | GT
class Eq a => Ord a where
compare :: Ord a => a -> a -> Ordering
(<) :: Ord a => a -> a -> Bool
(<=) :: Ord a => a -> a -> Bool
(>) :: Ord a => a -> a -> Bool
(>=) :: Ord a => a -> a -> Bool
min :: Ord a => a -> a -> a
max :: Ord a => a -> a -> a
Alle Methoden folgend compare
kann davon in einer Reihe von Möglichkeiten abgeleitet werden:
x < y = compare x y == LT
x <= y = x < y || x == y -- Note the use of (==) inherited from Eq
x > y = not (x <= y)
x >= y = not (x < y)
min x y = case compare x y of
EQ -> x
LT -> x
GT -> y
max x y = case compare x y of
EQ -> x
LT -> y
GT -> x
Typklassen, die Ord
selbst erweitern, müssen mindestens die compare
Methode oder die Methode (<=)
selbst implementieren, wodurch das gerichtete Vererbungsgitter aufgebaut wird.
Gl
Alle grundlegenden Datentypen (wie Int
, String
, Eq a => [a]
) von Prelude außer für Funktionen und IO
haben Instanzen von Eq
. Wenn ein Typ Eq
instanziiert, bedeutet dies, dass wir wissen, wie zwei Werte für den Wert oder die strukturelle Gleichheit zu vergleichen sind.
> 3 == 2
False
> 3 == 3
True
Erforderliche Methoden
-
(==) :: Eq a => a -> a -> Boolean
oder(/=) :: Eq a => a -> a -> Boolean
(wenn nur einer implementiert ist, wird der andere standardmäßig auf die Negation gesetzt definiertes ein)
Definiert
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Direkte Superklassen
Keiner
Bemerkenswerte Unterklassen
Ord
Typen instanziieren Ord
umfassen zB Int
, String
, und [a]
(für die Typen a
, wo es eine ist Ord a
Beispiel). Wenn ein Typ instanziiert Ord
es bedeutet , dass wir eine „natürliche“ Ordnung der Werte dieses Typs kennen. Man beachte, dass es oft viele Möglichkeiten gibt, die „natürliche“ Anordnung eines Typs zu Ord
und Ord
zwingt uns, einen zu bevorzugen.
Ord
stellt die Standardoperatoren (<=)
, (<)
, (>)
, (>=)
bereit, definiert sie jedoch alle interessant mit einem benutzerdefinierten algebraischen Datentyp
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Erforderliche Methoden
-
compare :: Ord a => a -> a -> Ordering
oder(<=) :: Ord a => a -> a -> Boolean
(die Standardeinstellung des Standardcompare
Methode verwendet(<=)
in der Umsetzung)
Definiert
-
compare :: Ord a => a -> a -> Ordering
-
(<=) :: Ord a => a -> a -> Boolean
-
(<) :: Ord a => a -> a -> Boolean
-
(>=) :: Ord a => a -> a -> Boolean
-
(>) :: Ord a => a -> a -> Boolean
-
min :: Ord a => a -> a -> a
-
max :: Ord a => a -> a -> a
Direkte Superklassen
Monoid
Zu den Typen, die Monoid
instantiieren, gehören unter anderem Listen, Zahlen und Funktionen mit Monoid
Rückgabewerten. Um ein Monoid
zu instanziieren, muss ein Typ eine assoziative binäre Operation ( mappend
oder (<>)
) unterstützen, die seine Werte kombiniert, und einen speziellen "Null" -Wert ( mempty
) haben, sodass das Kombinieren eines Werts diesen Wert nicht ändert:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Monoid
Typen sind intuitiv " Monoid
", da sie das Anhängen von Werten zusammen unterstützen. Alternativ können Monoid
Typen als Folgen von Werten betrachtet werden, für die die Reihenfolge wichtig ist, nicht aber für die Gruppierung. Zum Beispiel ist ein binärer Baum ein Monoid
, aber die Verwendung von Monoid
Operationen können wir keine Verzweigungsstruktur Zeuge, nur eine Traversal seiner Werte (siehe Foldable
und Traversable
).
Erforderliche Methoden
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Direkte Superklassen
Keiner
Num
Die allgemeinste Klasse für Zahlentypen, genauer für Ringe , dh Zahlen, die im üblichen Sinne addiert und subtrahiert und multipliziert werden können, jedoch nicht unbedingt geteilt werden müssen.
Diese Klasse enthält sowohl Integraltypen ( Int
, Integer
, Word32
usw.) als auch gebrochene Typen ( Double
, Rational
, auch komplexe Zahlen usw.). Bei endlichen Typen wird die Semantik im Allgemeinen als modulare Arithmetik verstanden , dh mit Über- und Unterlauf † .
Beachten Sie, dass die Regeln für die numerischen Klassen viel weniger strikt eingehalten werden als die Monaden- oder Monoidgesetze oder die für den Gleichheitsvergleich . Insbesondere gehorchen Fließkommazahlen im Allgemeinen nur annähernd den Gesetzen.
Die Methoden
fromInteger :: Num a => Integer -> a
. Konvertieren Sie eine Ganzzahl in den allgemeinen Zahlentyp (ggf. Umbruch des Bereichs). Haskell- Zahlenliterale können als monomorphesInteger
Literal mit der allgemeinen Konvertierung verstanden werden. Sie können also das Literal5
sowohl in einemInt
Kontext als auch in einerComplex Double
.(+) :: Num a => a -> a -> a
. Standardaddition, im Allgemeinen als assoziativ und kommutativ verstanden, dha + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Subtraktion, die die Umkehrung der Addition ist:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Multiplikation, eine assoziative Operation, die über Addition verteilt ist:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
In den meisten Fällen ist die Multiplikation auch kommutativ, aber dies ist definitiv keine Anforderung.
negate :: Num a => a -> a
. Der vollständige Name des unären Negationsoperators.-1
ist syntaktischer Zucker fürnegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. Die Absolutwertfunktion liefert immer ein nicht negatives Ergebnis der gleichen Größeabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
sollte nur dann passieren, wenna ≡ 0
.Für echte Typen ist klar, was nicht negativ bedeutet: Sie haben immer
abs a >= 0
. Komplexe etc.-Typen haben keine genau definierte Reihenfolge, jedoch sollte das Ergebnis vonabs
immer in der realen Teilmenge ‡ liegen (dh geben Sie eine Zahl an, die auch als einzelnes Zahlenliteral ohne Negation geschrieben werden könnte).signum :: Num a => a -> a
. Die Vorzeichenfunktion ergibt dem Namen zufolge nur-1
oder1
, abhängig vom Vorzeichen des Arguments. Das trifft tatsächlich nur für reelle Zahlen ungleich Null zu. im Allgemeinen wirdsignum
besser als normalisierende Funktion verstanden:abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Beachten Sie, dass Abschnitt 6.4.4 des Haskell 2010-Berichts explizit verlangt, dass diese letzte Gleichheit für jede gültige
Num
Instanz gilt.
Einige Bibliotheken, insbesondere linear und hmatrix , haben ein viel laxereres Verständnis für die Klasse Num
: Sie behandeln sie nur als eine Möglichkeit, die Rechenoperatoren zu überlasten . Während dies für +
und -
ziemlich unkompliziert ist, wird es bereits mit *
problematisch und mit den anderen Methoden umso mehr. Sollte zum Beispiel *
Matrixmultiplikation oder elementweise Multiplikation bedeuten?
Es ist wohl eine schlechte Idee, solche Instanzen ohne Zahlen zu definieren. Bitte beachten Sie dedizierte Klassen wie VectorSpace
.
† Insbesondere werden die "Negative" von vorzeichenlosen Typen in ein großes positives (-4 :: Word32) == 4294967292
, z. B. (-4 :: Word32) == 4294967292
.
‡ Dies wird weitgehend nicht erfüllt: Vektortypen haben keine echte Untermenge. Die kontroversen Num
Instanzen für solche Typen definieren in der Regel abs
und signum
elementweise, was mathematisch gesehen nicht wirklich sinnvoll ist.