Haskell Language
Typ Familien
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.