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 -> Booleanoder(/=) :: 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 -> Orderingoder(<=) :: Ord a => a -> a -> Boolean(die Standardeinstellung des StandardcompareMethode 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 monomorphesIntegerLiteral mit der allgemeinen Konvertierung verstanden werden. Sie können also das Literal5sowohl in einemIntKontext 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 * cIn 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.-1ist syntaktischer Zucker fürnegate 1.-a ≡ negate a ≡ 0 - aabs :: Num a => a -> a. Die Absolutwertfunktion liefert immer ein nicht negatives Ergebnis der gleichen Größeabs (-a) ≡ abs a abs (abs a) ≡ abs aabs a ≡ 0sollte 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 vonabsimmer 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-1oder1, abhängig vom Vorzeichen des Arguments. Das trifft tatsächlich nur für reelle Zahlen ungleich Null zu. im Allgemeinen wirdsignumbesser 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 instancesBeachten Sie, dass Abschnitt 6.4.4 des Haskell 2010-Berichts explizit verlangt, dass diese letzte Gleichheit für jede gültige
NumInstanz 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.
