Haskell Language
Digitare famiglie
Ricerca…
Digitare le famiglie di sinonimi
Le famiglie di sinonimi dei sinonimi sono solo funzioni di tipo livello: associano i tipi di parametro ai tipi di risultati. Questi sono disponibili in tre diverse varietà.
Famiglie chiuse di tipo-sinonimo
Funzionano molto come le normali funzioni Haskell a livello di valore: si specificano alcune clausole, mappando alcuni tipi ad altri:
{-# 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
Aprire famiglie di tipi-sinonimo
Questi funzionano più come esempi di classi di tipografia: chiunque può aggiungere più clausole in altri moduli.
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.
Sinonimi di tipo associato alla classe
Una famiglia di tipo aperto può anche essere combinata con una classe reale. Questo di solito viene fatto quando, come con le famiglie di dati associati , alcuni metodi di classe necessitano di oggetti helper aggiuntivi e questi oggetti helper possono essere diversi per istanze diverse ma possono anche essere condivisi. Un buon esempio è la classe 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
Nota come nei primi due casi, l'implementazione di Scalar
è la stessa. Ciò non sarebbe possibile con una famiglia di dati associata: le famiglie di dati sono iniettive , le famiglie di sinonimo di tipo non lo sono.
Mentre la non-iniettività apre alcune possibilità come sopra, rende anche più difficile l'inferenza di tipo. Ad esempio, il seguente comando non digiterà:
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 questo caso, il compilatore non può sapere quale istanza usare, perché l'argomento su bar
è di per sé solo un letterale polimorfico Num
. E la funzione funzione Bar
non può essere risolta in "direzione inversa", precisamente perché non è iniettiva † e quindi non invertibile (potrebbe esserci più di un tipo con Bar a = String
).
† Con solo queste due istanze, è in realtà iniettivo, ma il compilatore non può sapere che qualcuno non aggiungerà più istanze in seguito e quindi rompere il comportamento.
Famiglie di tipi di dati
Le famiglie di dati possono essere utilizzate per creare tipi di dati con implementazioni diverse in base agli argomenti dei tipi.
Famiglie di dati autonomi
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
Nella dichiarazione precedente, Nil :: List Char
e UnitList :: Int -> List ()
Famiglie di dati associate
Le famiglie di dati possono anche essere associate a tipeclass. Questo è spesso utile per i tipi con "oggetti helper", che sono necessari per i metodi generici di tipizzazione, ma devono contenere informazioni diverse a seconda dell'istanza concreta. Ad esempio, le posizioni di indicizzazione in un elenco richiedono solo un numero singolo, mentre in un albero è necessario un numero per indicare il percorso in ciascun nodo:
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
iniettività
Le famiglie di tipo non sono necessariamente iniettive. Pertanto, non possiamo dedurre il parametro da un'applicazione. Ad esempio, in servant
, dato un tipo Server a
non possiamo inferire il tipo a
. Per risolvere questo problema, possiamo usare Proxy
. Ad esempio, in servant
, la funzione serve
ha tipo ... Proxy a -> Server a -> ...
Possiamo dedurre a
da Proxy a
perché il Proxy
è definito da data
che sono iniettivi.