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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow