Haskell Language
Wpisz rodziny
Szukaj…
Wpisz rodziny synonimów
Rodziny synonimów typów są tylko funkcjami na poziomie typu: kojarzą typy parametrów z typami wyników. Występują w trzech różnych odmianach.
Zamknięte rodziny synonimów typów
Działają one podobnie jak zwykłe funkcje Haskell na poziomie wartości: określasz niektóre klauzule, mapując niektóre typy na inne:
{-# 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
Otwórz rodziny synonimów typów
Działają one bardziej jak instancje typów: każdy może dodać więcej klauzul w innych modułach.
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.
Synonimy typów związane z klasą
Rodzinę typu otwartego można również łączyć z rzeczywistą klasą. Zwykle dzieje się tak, gdy, podobnie jak w przypadku powiązanych rodzin danych , niektóre metody klasy wymagają dodatkowych obiektów pomocniczych, a te obiekty pomocnicze mogą być różne dla różnych instancji, ale być może mogą być również współużytkowane. Dobrym przykładem jest klasa VectorSpace
:
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
Zwróć uwagę, że w dwóch pierwszych przypadkach implementacja Scalar
jest taka sama. Nie byłoby to możliwe w powiązanej rodzinie danych: rodziny danych są iniekcyjne , a rodziny synonimów nie.
Chociaż brak iniekcji otwiera pewne możliwości, takie jak powyższe, utrudnia również wnioskowanie typu. Na przykład następujące czynności nie sprawdzą typu:
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)
W takim przypadku kompilator nie może wiedzieć, której instancji użyć, ponieważ sam argument to bar
jest po prostu polimorficznym literałem Num
. A funkcja typu Bar
nie może być rozwiązana w „odwrotnym kierunku”, właśnie dlatego, że nie jest iniekcyjna †, a zatem nie jest odwracalna (może istnieć więcej niż jeden typ z Bar a = String
).
† Tylko z tymi dwoma wystąpieniami jest w rzeczywistości iniekcyjny, ale kompilator nie może wiedzieć, że ktoś nie doda więcej wystąpień później, a tym samym zepsuje zachowanie.
Rodziny typów danych
Rodzin danych można używać do budowania typów danych, które mają różne implementacje w zależności od argumentów typu.
Samodzielne rodziny danych
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
W powyższej deklaracji Nil :: List Char
i UnitList :: Int -> List ()
Powiązane rodziny danych
Rodziny danych można również powiązać z typami klas. Jest to często przydatne w przypadku typów z „obiektami pomocniczymi”, które są wymagane w przypadku ogólnych metod klasowych, ale muszą zawierać różne informacje w zależności od konkretnego wystąpienia. Na przykład indeksowanie lokalizacji na liście wymaga tylko jednego numeru, podczas gdy w drzewie potrzebny jest numer wskazujący ścieżkę w każdym węźle:
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
Iniekcyjność
Rodzaje typów niekoniecznie są iniekcyjne. Dlatego nie możemy wywnioskować parametru z aplikacji. Na przykład w servant
, biorąc pod uwagę typ Server a
nie możemy wnioskować o typie a
. Aby rozwiązać ten problem, możemy użyć Proxy
. Na przykład w servant
funkcja serve
ma typ ... Proxy a -> Server a -> ...
Możemy wywnioskować od a
Proxy a
ponieważ Proxy
jest określony przez data
, które jest injective.