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.

Beziehungen zwischen Standard-Haskell-Klassen, Abbildung 1, veröffentlicht in Typeclassopedia.

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 Standard compare 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 monomorphes Integer Literal mit der allgemeinen Konvertierung verstanden werden. Sie können also das Literal 5 sowohl in einem Int Kontext als auch in einer Complex Double .

  • (+) :: Num a => a -> a -> a . Standardaddition, im Allgemeinen als assoziativ und kommutativ verstanden, dh

      a + (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ür negate 1 .

      -a ≡ negate a ≡ 0 - a
    
  • abs :: Num a => a -> a . Die Absolutwertfunktion liefert immer ein nicht negatives Ergebnis der gleichen Größe

      abs (-a) ≡ abs a
      abs (abs a) ≡ abs a
    

    abs a ≡ 0 sollte nur dann passieren, wenn a ≡ 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 von abs 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 oder 1 , abhängig vom Vorzeichen des Arguments. Das trifft tatsächlich nur für reelle Zahlen ungleich Null zu. im Allgemeinen wird signum 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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow