Haskell Language
Type Families
Zoeken…
Type Synoniem Gezinnen
Type synoniemenfamilies zijn slechts functies op type niveau: ze koppelen parametertypen aan resultatietypen. Deze zijn er in drie verschillende varianten.
Gesloten type-synoniemfamilies
Deze werken op dezelfde manier als gewone Haskell-functies op waardeniveau: u geeft enkele clausules op, waarbij bepaalde typen aan andere worden toegewezen:
{-# 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
Open type-synoniemenfamilies
Deze werken meer als voorbeelden van typeclass: iedereen kan meer clausules toevoegen in andere modules.
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.
Klasse-geassocieerde type synoniemen
Een open type familie kan ook worden gecombineerd met een echte klasse. Dit wordt meestal gedaan wanneer, net als met de bijbehorende gegevens families , wat klasse methode moet extra helper objecten, en deze helper objecten kan verschillend zijn voor verschillende instanties, maar kan eventueel ook gedeeld. Een goed voorbeeld is de 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
Merk op dat in de eerste twee gevallen de implementatie van Scalar
hetzelfde is. Dit zou niet mogelijk zijn met een bijbehorende gegevensfamilie: gegevensfamilies zijn injectief , type-synoniemenfamilies niet.
Hoewel niet-injectiviteit sommige mogelijkheden zoals de bovenstaande opent, maakt het ook type-inferentie moeilijker. Het volgende zal bijvoorbeeld geen typecontrole uitvoeren:
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 dit geval kan de compiler niet weten welke instantie hij moet gebruiken, omdat het argument om te bar
zelf slechts een polymorf Num
letterlijk is. En de typefunctie Bar
kan niet in "omgekeerde richting" worden opgelost, juist omdat het niet injectief is † en dus niet omkeerbaar is (er kan meer dan één type zijn met Bar a = String
).
† Met slechts deze twee gevallen is het eigenlijk injectief, maar de compiler kan niet weten zal iemand niet meer gevallen toe te voegen later op en daarmee het gedrag te doorbreken.
Datatype Families
Datafamilies kunnen worden gebruikt om datatypes te bouwen die verschillende implementaties hebben op basis van hun typeargumenten.
Standalone gegevensfamilies
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
In de bovenstaande verklaring, Nil :: List Char
en UnitList :: Int -> List ()
Bijbehorende datafamilies
Datafamilies kunnen ook worden geassocieerd met typeclasses. Dit is vaak handig voor typen met "helperobjecten", die vereist zijn voor generieke typeclass-methoden, maar die verschillende informatie moeten bevatten, afhankelijk van de concrete instantie. Voor het indexeren van locaties in een lijst is bijvoorbeeld slechts één nummer vereist, terwijl u in een structuur een nummer nodig hebt om het pad bij elk knooppunt aan te geven:
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
injectiviteit
Type Families zijn niet noodzakelijk injectief. Daarom kunnen we de parameter niet afleiden uit een toepassing. In servant
kunnen we bijvoorbeeld een type Server a
niet afleiden uit het type a
. Om dit probleem op te lossen, kunnen we Proxy
. In servant
heeft de serve
functie bijvoorbeeld het type ... Proxy a -> Server a -> ...
We kunnen afleiden a
van Proxy a
omdat Proxy
wordt bepaald door data
die injectief.