Suche…


Geben Sie Synonymfamilien ein

Typensynonymfamilien sind nur Funktionen auf Typebene: Sie ordnen Parametertypen Ergebnistypen zu. Diese gibt es in drei verschiedenen Varianten.

Geschlossene Typ-Synonym-Familien

Diese funktionieren ähnlich wie normale Haskell-Funktionen auf Wertebene: Sie geben einige Klauseln an und ordnen bestimmte Typen anderen zu:

{-# LANGUAGE TypeFamilies #-}
type family Vanquisher a where
    Vanquisher Rock = Paper
    Vanquisher Paper = Scissors
    Vanquisher Scissors = Rock

data Rock=Rock; data Paper=Paper; data Scissors=Scissors

Offene Typ-Synonym-Familien

Diese funktionieren eher wie Typklasseninstanzen: Jeder kann weitere Klauseln in anderen Modulen hinzufügen.

type family DoubledSize w

type instance DoubledSize Word16 = Word32
type instance DoubledSize Word32 = Word64
-- Other instances might appear in other modules, but two instances cannot overlap
-- in a way that would produce different results.

Klassenbegleitende Typ Synonyme

Eine offene Typfamilie kann auch mit einer tatsächlichen Klasse kombiniert werden. Dies wird normalerweise durchgeführt, wenn wie bei den zugehörigen Datenfamilien einige Klassenmethoden zusätzliche Hilfsobjekte benötigen. Diese Hilfsobjekte können für verschiedene Instanzen unterschiedlich sein, möglicherweise aber auch gemeinsam genutzt werden. Ein gutes Beispiel ist die VectorSpace Klasse :

class VectorSpace v where
  type Scalar v :: *
  (*^) :: Scalar v -> v -> v

instance VectorSpace Double where
  type Scalar Double = Double
  μ *^ n = μ * n

instance VectorSpace (Double,Double) where
  type Scalar (Double,Double) = Double
  μ *^ (n,m) = (μ*n, μ*m)
  
instance VectorSpace (Complex Double) where
  type Scalar (Complex Double) = Complex Double
  μ *^ n = μ*n

Beachten Sie, dass die Implementierung von Scalar in den ersten beiden Fällen identisch ist. Dies wäre mit einer zugehörigen Datenfamilie nicht möglich: Datenfamilien sind injektiv , Typ-Synonym-Familien nicht.

Die Nichtinjektivität eröffnet zwar einige Möglichkeiten wie die oben genannten, erschwert aber auch die Typinferenz. Zum Beispiel wird Folgendes nicht überprüft:

class Foo a where
  type Bar a :: *
  bar :: a -> Bar a
instance Foo Int where
  type Bar Int = String
  bar = show
instance Foo Double where
  type Bar Double = Bool
  bar = (>0)

main = putStrLn (bar 1)

In diesem Fall kann der Compiler nicht wissen, welche Instanz verwendet werden soll, da das bar Argument selbst nur ein polymorphes Num Literal ist. Und der Typ Funktion Bar kann nicht in „umgekehrter Richtung“ gelöst werden, gerade weil es nicht ist injektiv und daher nicht umkehrbar (es könnte mehr als eine Art sein , mit Bar a = String ).


Mit nur diesen beiden Fällen ist es tatsächlich injektiv, aber der Compiler kann nicht jemand weiß mehr Instanzen nicht später hinzufügen und dadurch das Verhalten brechen.

Datentyp-Familien

Datenfamilien können zum Erstellen von Datentypen verwendet werden, die basierend auf ihren Typargumenten unterschiedliche Implementierungen aufweisen.

Standalone-Datenfamilien

{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int

In der obigen Deklaration Nil :: List Char und UnitList :: Int -> List ()

Zugehörige Datenfamilien

Datenfamilien können auch mit Typklassen verknüpft werden. Dies ist häufig hilfreich für Typen mit "Hilfsobjekten", die für generische Typenklassenmethoden erforderlich sind, jedoch je nach konkreter Instanz unterschiedliche Informationen enthalten müssen. Zum Beispiel erfordert das Indexieren von Speicherorten in einer Liste nur eine einzige Nummer, während Sie in einer Baumstruktur eine Nummer benötigen, um den Pfad an jedem Knoten anzugeben:

class Container f where
  data Location f
  get :: Location f -> f a -> Maybe a

instance Container [] where
  data Location [] = ListLoc Int
  get (ListLoc i) xs
    | i < length xs  = Just $ xs!!i
    | otherwise      = Nothing

instance Container Tree where
  data Location Tree = ThisNode | NodePath Int (Location Tree)
  get ThisNode (Node x _) = Just x
  get (NodePath i path) (Node _ sfo) = get path =<< get i sfo

Injektion

Typfamilien sind nicht unbedingt injektiv. Daher können wir den Parameter nicht aus einer Anwendung ableiten. Zum Beispiel können wir in servant bei einem Typ Server a nicht auf den Typ a . Um dieses Problem zu lösen, können wir Proxy . Zum Beispiel hat die serve Funktion in servant den Typ ... Proxy a -> Server a -> ... Wir können auf a von Proxy a da Proxy durch data definiert wird data die injektiv sind.



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