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.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow