Este post ha sido probado por nuestros especialistas para asegurar la veracidad de nuestra esta división.
Haskell 98 permette al programmatore di aggiungere "deriving( Eq, Ord )
" a una dichiarazione di tipo di dati, per generare una dichiarazione di istanza standard per le classi specificate nella classe deriving
. In Haskell 98, le uniche classi che possono comparire nella clausola deriving
sono le classi standard Eq
, Ord
, Enum
, Ix
, Bounded
, Read
, e Show
.
GHC estende questo elenco con molte altre classi che possono essere derivate automaticamente:
- Con
DeriveGeneric
è possibile derivare istanze delle classiGeneric
eGeneric1
, definite inGHC.Generics
. Si possono usare per definire funzioni generiche, come descritto in Programmazione generica. - Con
DeriveFunctor
è possibile derivare istanze della classeFunctor
definita inGHC.Base
. - Con
DeriveDataTypeable
è possibile derivare istanze della classeData
definita inData.Data
. - Con
DeriveFoldable
è possibile derivare istanze della classeFoldable
definita inData.Foldable
. - Con
DeriveTraversable
è possibile derivare istanze della classeTraversable
definita inData.Traversable
. Poiché la classeTraversable
detta le istanze diFunctor
eFoldable
probabilmente si vorrà derivare anche loro, quindiDeriveTraversable
implicaDeriveFunctor
eDeriveFoldable
. - Con
DeriveLift
è possibile derivare istanze della classeLift
, definita nella classeLanguage.Haskell.TH.Syntax
del modulotemplate-haskell
del pacchetto.
Si può anche usare una dichiarazione di derivazione autonoma (vedere Dichiarazioni di derivazione autonome).
In ogni caso, la classe appropriata deve essere in ambito prima di poter essere menzionata nell'elemento deriving
.
6.6.4.1. Derivazione Functor
istanze
DeriveFunctor
-
Da: 7.10.1 Consente la derivazione automatica di istanze per l'oggetto
Functor
della classe tipo.
Con DeriveFunctor
si può derivare Functor
per i tipi di dati del tipo Type -> Type
. Ad esempio, questa dichiarazione:
data Example a = Ex a Char (Example a) (Example Char) deriving Functor
genererebbe la seguente istanza:
instance Functor Example where fmap f (Ex a1 a2 a3 a4) = Ex (f a1) a2 (fmap f a3) a4
L'algoritmo di base per DeriveFunctor
esamina gli argomenti di ciascun costruttore di un tipo di dati, applicando una funzione di mappatura a seconda del tipo di ciascun argomento. Se viene trovata una variabile di tipo semplice che è sintatticamente equivalente all'ultimo parametro di tipo del tipo di dati (a
nell'esempio precedente), si applica la funzione f
direttamente ad essa. Se si incontra un tipo che non è sintatticamente equivalente all'ultimo parametro di tipo ma che menziona l'ultimo parametro di tipo da qualche parte, allora una chiamata ricorsiva a fmap
viene effettuata. Se viene trovato un tipo che non menziona affatto l'ultimo parametro del tipo, viene lasciato in pace.
Il secondo caso, in cui un tipo non è uguale al parametro del tipo ma contiene il parametro del tipo, può essere sorprendentemente complicato. Per esempio, il seguente esempio viene compilato:
newtype Right a = Right (Either Int a) deriving Functor
Modificando leggermente il codice, tuttavia, si ottiene un codice che non si compila:
newtype Wrong a = Wrong (Either a Int) deriving Functor
La differenza riguarda il posizionamento dell'ultimo parametro di tipo, a
. Nel parametro Right
è il caso di, a
si verifica all'interno del tipo Either Int a
e, inoltre, appare come ultimo argomento di tipo di Either
. Nel tipo Wrong
tuttavia, il caso di Wrong
, a
non è l'ultimo argomento di tipo a Either
; piuttosto, Int
è.
Questa distinzione è importante per il modo in cui DeriveFunctor
funziona. Il derivato Functor Right
sarebbe:
instance Functor Right where fmap f (Right a) = Right (fmap f a)
Dato un valore di tipo Right a
GHC deve produrre un valore di tipo Right b
. Poiché l'argomento del metodo Right
ha il tipo Either Int a
il codice chiama ricorsivamente fmap
per produrre un valore di tipo Either Int b
che viene utilizzato a sua volta per costruire un valore finale di tipo Right b
.
Il codice generato per l'elemento Functor Wrong
sarebbe esattamente lo stesso, tranne che per la presenza di Wrong
che sostituisce ogni occorrenza di Right
. Il problema è che ora fmap
viene applicato ricorsivamente a un valore di tipo Either a Int
. Questo non può produrre un valore di tipo Either b Int
, in quanto fmap
può cambiare solo l'ultimo parametro di tipo! Questo fa sì che il codice generato sia mal tipizzato.
Come regola generale, se un tipo di dati ha un parametro derivato Functor
e il suo ultimo parametro di tipo si trova sul lato destro della dichiarazione dei dati, allora deve (1) essere nudo (ad esempio, newtype Id a = Id a
), oppure (2) essere l'ultimo parametro di un costruttore di tipi (come in Right
sopra).
Ci sono due eccezioni a questa regola:
-
Tipi di tupla. Quando una tupla non unitaria è usata sul lato destro di una dichiarazione di dati,
DeriveFunctor
la tratta come un prodotto di tipi distinti. In altre parole, il codice seguente:newtype Triple a = Triple (a, Int, [a]) deriving Functor
risulterebbe in un codice generato
Functor
generata in questo modo:instance Functor Triple where fmap f (Triple a) = Triple (case a of (a1, a2, a3) -> (f a1, a2, fmap f a3))
Cioè,
DeriveFunctor
esegue il pattern-matching delle tuple e mappa ogni tipo che costituisce la tupla. Il codice generato ricorda quello che verrebbe generato dadata Triple a = Triple a Int [a]
ma con un macchinario aggiuntivo per gestire le tuple. -
Tipi di funzione. L'ultimo parametro del tipo può apparire in qualsiasi punto di un tipo di funzione, purché si trovi in un elemento covariante posizione. Per illustrare cosa significa, si considerino i tre esempi seguenti:
newtype CovFun1 a = CovFun1 (Int -> a) deriving Functor newtype CovFun2 a = CovFun2 ((a -> Int) -> a) deriving Functor newtype CovFun3 a = CovFun3 (((Int -> a) -> Int) -> a) deriving Functor
Tutti e tre questi esempi verrebbero compilati senza problemi. D'altra parte:
newtype ContraFun1 a = ContraFun1 (a -> Int) deriving Functor newtype ContraFun2 a = ContraFun2 ((Int -> a) -> Int) deriving Functor newtype ContraFun3 a = ContraFun3 (((a -> Int) -> a) -> Int) deriving Functor
Sebbene questi esempi sembrino simili, nessuno di essi verrebbe compilato con successo. Questo perché tutte le occorrenze dell'ultimo parametro di tipo
a
si verificano in contravariante posizioni, non in quelle covarianti.Intuitivamente, un tipo covariante è prodotto e un tipo controvariante è consumato. La maggior parte dei tipi in Haskell sono covarianti, ma il tipo di funzione è speciale in quanto il lato sinistro di una freccia di funzione inverte la varianza. Se un tipo di funzione
a -> b
appare in una posizione covariante (ad es,CovFun1
sopra), alloraa
è in posizione contravariante eb
è in posizione covariante. Allo stesso modo, sea -> b
appare in una posizione contravariante (ad es,CovFun2
sopra), alloraa
si trova ina
posizione covariante eb
è in posizione contravariante.Per capire perché un tipo di dati con un'occorrenza contravariante del suo ultimo parametro di tipo non può avere una derivata
Functor
derivato, supponiamo che un tipo di datiFunctor ContraFun1
esista. L'implementazione sarebbe simile a questa:instance Functor ContraFun1 where fmap f (ContraFun g) = ContraFun (x -> _)
Abbiamo
f :: a -> b
,g :: a -> Int
ex :: b
. Utilizzando questi, dobbiamo in qualche modo riempire il buco (indicato con un trattino basso) con un valore del tipoInt
. Quali sono le nostre opzioni?Possiamo provare ad applicare
g
ax
. Questo però non funzionerà, perchég
si aspetta un argomento di tipoa
ex :: b
. Ancora peggio, non possiamo trasformarex
in qualcosa di tipoa
, poichéf
ha bisogno di un argomento di tipoa
! In breve, non c'è un buon modo per farlo funzionare.D'altra parte, una funzione derivata
Functor
per la classeCovFun
rientrano nell'ambito delle possibilità:instance Functor CovFun1 where fmap f (CovFun1 g) = CovFun1 (x -> f (g x)) instance Functor CovFun2 where fmap f (CovFun2 g) = CovFun2 (h -> f (g (x -> h (f x)))) instance Functor CovFun3 where fmap f (CovFun3 g) = CovFun3 (h -> f (g (k -> h (x -> f (k x)))))
Ci sono altri scenari in cui un'istanza derivata di Functor
non riuscirà a compilare:
- Un tipo di dati non ha parametri di tipo (per esempio,
data Nothing = Nothing
). - L'ultima variabile di tipo di un tipo di dati è usata in un'istanza di tipo
DatatypeContexts
(ad es,data Ord a => O a = O a
). -
L'ultima variabile di tipo di un tipo di dati è usata in un vincolo
ExistentialQuantification
o è raffinata in un GADT. Ad esempio,data T a b where T4 :: Ord b => b -> T a b T5 :: b -> T b b T6 :: T a (b,b) deriving instance Functor (T a)
non verrebbe compilato con successo a causa del modo in cui
b
è vincolato.
Quando l'ultimo parametro di tipo ha un ruolo fantasma (vedi Ruoli), il parametro derivato Functor
derivato non verrà prodotto utilizzando il solito algoritmo. Al contrario, l'intero valore sarà forzato.
data Phantom a = Z | S (Phantom a) deriving Functor
produrrà la seguente istanza:
instance Functor Phantom where fmap _ = coerce
Quando un tipo non ha costruttori, il derivato Functor
derivata forzerà semplicemente il valore (inferiore) dell'argomento, utilizzando il metodo EmptyCase
.
data V a deriving Functor type role V nominal
produrrà
- istanza Functor V dove
- fmap _ z = caso z di
6.6.4.2. Derivazione Foldable
istanze
DeriveFoldable
-
Da: 7.10.1 Consente la derivazione automatica di istanze per l'elemento
Foldable
della classe tipo.
Con DeriveFoldable
si può derivare Foldable
per i tipi di dati del tipo Type -> Type
. Ad esempio, questa dichiarazione:
data Example a = Ex a Char (Example a) (Example Char) deriving Foldable
genererebbe la seguente istanza:
instance Foldable Example where foldr f z (Ex a1 a2 a3 a4) = f a1 (foldr f z a3) foldMap f (Ex a1 a2 a3 a4) = mappend (f a1) (foldMap f a3)
L'algoritmo per DeriveFoldable
è adattato dall'algoritmo DeriveFunctor
ma genera le definizioni per foldMap
, foldr
e null
invece di fmap
. Inoltre, DeriveFoldable
filtra tutti gli argomenti del costruttore nell'espressione RHS i cui tipi non menzionano l'ultimo parametro di tipo, poiché questi argomenti non devono essere ripiegati.
Quando il parametro del tipo ha un ruolo fantasma (vedere Ruoli), DeriveFoldable
deriva un'istanza banale. Ad esempio, questa dichiarazione:
data Phantom a = Z | S (Phantom a)
genererà la seguente istanza.
instance Foldable Phantom where foldMap _ _ = mempty
Allo stesso modo, quando il tipo non ha costruttori, DeriveFoldable
genererà un'istanza banale:
data V a deriving Foldable type role V nominal
genererà la seguente.
instance Foldable V where foldMap _ _ = mempty
Ecco le differenze tra il codice generato per Functor
e Foldable
:
#. Quando una variabile di tipo nudo a
viene incontrata, DeriveFunctor
genererebbe f a
per un fmap
definizione. DeriveFoldable
genererebbe f a z
per foldr
, f a
per foldMap
e False
per null
.
-
Quando un tipo che non è sintatticamente equivalente a
a
ma che contienea
, si incontra un tipo che non è sintatticamente equivalente aa
, ma che contienea
,DeriveFunctor
chiama ricorsivamentefmap
su di esso. Allo stesso modo,DeriveFoldable
chiamerebbe ricorsivamentefoldr
efoldMap
. A seconda del contesto,null
può chiamare ricorsivamentenull
oall null
. Ad esempio, datodata F a = F (P a) data G a = G (P (a, Int)) data H a = H (P (Q a))
Foldable
la derivazione produrrànull (F x) = null x null (G x) = null x null (H x) = all null x
-
DeriveFunctor
rimette tutto insieme alla fine, invocando il costruttore.DeriveFoldable
invece, costruisce un valore di qualche tipo. Perfoldr
questo si ottiene concatenando le applicazioni dif
e ricorsivofoldr
sul valore di statoz
. PerfoldMap
Questo avviene combinando tutti i valori conmappend
. Pernull
, i valori sono solitamente combinati con&&
. Tuttavia, se uno dei valori è noto per essereFalse
, tutti gli altri verranno eliminati. Ad esempio,data SnocList a = Nil | Snoc (SnocList a) a
non produrrà
null (Snoc xs _) = null xs && False
(che camminerebbe sull'intero elenco), ma piuttosto
null (Snoc _ _) = False
Ci sono altre differenze per quanto riguarda i tipi di dati che possono essere derivati Foldable
istanze:
- I tipi di dati che contengono tipi di funzione sul lato destro non possono avere istanze derivate
Foldable
.Foldable
istanze. -
Foldable
possono essere derivate per i tipi di dati in cui l'ultimo parametro del tipo è vincolato esistenzialmente o raffinato in un GADT. Ad esempio, questo tipo di dati:data E a where E1 :: (a ~ Int) => a -> E a E2 :: Int -> E Int E3 :: (a ~ Int) => a -> E Int E4 :: (a ~ Int) => Int -> E a deriving instance Foldable E
avrebbe le seguenti istanze generate
Foldable
istanza:instance Foldable E where foldr f z (E1 e) = f e z foldr f z (E2 e) = z foldr f z (E3 e) = z foldr f z (E4 e) = z foldMap f (E1 e) = f e foldMap f (E2 e) = mempty foldMap f (E3 e) = mempty foldMap f (E4 e) = mempty
Si noti come ogni costruttore di
E
utilizza una sorta di quantificazione esistenziale, ma solo l'argomento diE1
è effettivamente "ripiegato". Questo perché abbiamo scelto deliberatamente di ripiegare solo sui tipi universalmente polimorfi che sono sintatticamente equivalenti all'ultimo parametro del tipo. In particolare:
- Non ripieghiamo sugli argomenti di
E1
oE4
perché anche se(a ~ Int)
,Int
non è sintatticamente equivalente aa
. - Non ripieghiamo sull'argomento di
E3
perchéa
non è universalmente polimorfo. Ila
inE3
è (implicitamente) quantificato esistenzialmente, quindi non è lo stesso dell'ultimo parametro di tipo diE
.
6.6.4.3. Derivazione Traversable
istanze
DeriveTraversable
-
Implica: DeriveFoldable
,DeriveFunctor
Dal momento che: 7.10.1 Consentire la derivazione automatica di istanze per l'oggetto
Traversable
della classe di tipi.
Con DeriveTraversable
si può derivare Traversable
per i tipi di dati del tipo Type -> Type
. Ad esempio, questa dichiarazione:
data Example a = Ex a Char (Example a) (Example Char) deriving (Functor, Foldable, Traversable)
genererebbe la seguente istanza Traversable
istanza:
instance Traversable Example where traverse f (Ex a1 a2 a3 a4) = fmap (b1 b3 -> Ex b1 a2 b3 a4) (f a1) <*> traverse f a3
L'algoritmo per DeriveTraversable
è adattato dal metodo DeriveFunctor
ma genera una definizione per traverse
invece di fmap
. Inoltre, DeriveTraversable
filtra tutti gli argomenti del costruttore nell'espressione RHS i cui tipi non menzionano l'ultimo parametro di tipo, poiché questi argomenti non producono alcun effetto in una traversale.
Quando il parametro del tipo ha un ruolo fantasma (vedere Ruoli), DeriveTraversable
costringe il suo argomento. Ad esempio, questa dichiarazione:
data Phantom a = Z | S (Phantom a) deriving Traversable
genererà la seguente istanza:
instance Traversable Phantom where traverse _ z = pure (coerce z)
Quando il tipo non ha costruttori, DeriveTraversable
genererà l'istanza più pigra che può.
data V a deriving Traversable type role V nominal
genererà quanto segue, utilizzando EmptyCase
:
instance Traversable V where traverse _ z = pure (case z of)
Ecco le differenze tra il codice generato in ciascuna estensione:
- Quando una variabile di tipo nudo
a
vengono incontrate entrambe le variabiliDeriveFunctor
eDeriveTraversable
genererebberof a
per unfmap
etraverse
rispettivamente. - Quando un tipo che non è sintatticamente equivalente a
a
ma che contienea
, si incontra un tipo che non è sintatticamente equivalente aa
, ma che contienea
,DeriveFunctor
chiama ricorsivamentefmap
su di esso. Allo stesso modo,DeriveTraversable
chiamerebbe ricorsivamentetraverse
. DeriveFunctor
e alla fine rimette tutto insieme invocando il costruttore.DeriveTraversable
fa qualcosa di simile, ma funziona in modoApplicative
concatenando tutto con(<*>)
.
A differenza di DeriveFunctor
, DeriveTraversable
non può essere utilizzato su tipi di dati contenenti un tipo di funzione sul lato destro.
Per una specifica completa degli algoritmi utilizzati in DeriveFunctor
, DeriveFoldable
e DeriveTraversable
, vedere questa pagina wiki.
6.6.4.4. Derivazione Data
istanze
DeriveDataTypeable
-
Da: 6.8.1 Abilita la derivazione automatica delle istanze per la classe
Data
classe di tipi
6.6.4.5. Derivazione Typeable
istanze
La classe Typeable
è molto speciale:
Typeable
è polimorfa (vedi Polimorfismo di tipo).- GHC ha un risolutore personalizzato per scaricare i vincoli che coinvolgono le classi
Typeable
e le istanze scritte a mano sono proibite. Questo assicura che il programmatore non possa sovvertire il sistema dei tipi scrivendo istanze fasulle. - Istanze derivate di
Typeable
possono essere dichiarate se l'elementoDeriveDataTypeable
ma vengono ignorate e potrebbero essere segnalate come errore in una versione successiva del compilatore. -
Le regole per risolvere
Typeable
sono le seguenti:-
Un costruttore di tipo concreto applicato ad alcuni tipi.
instance (Typeable t1, .., Typeable t_n) => Typeable (T t1 .. t_n)
Questa regola funziona per qualsiasi costruttore di tipo concreto, compresi i costruttori di tipo con tipi polimorfi. L'unica restrizione è che se il costruttore di tipi ha un tipo polimorfico, allora deve essere applicato a tutti i suoi parametri di tipo e questi tipi devono essere concreti (cioè, non possono menzionare variabili di tipo).
-
Una variabile di tipo applicata ad alcuni tipi:
instance (Typeable f, Typeable t1, .., Typeable t_n) => Typeable (f t1 .. t_n)
-
Un tipo concreto letterale..:
instance Typeable 0 -- Type natural literals instance Typeable "Hello" -- Type-level symbols
-
6.6.4.6. Derivazione Lift
istanze
DeriveLift
-
Da: 8.0.1 Abilita la derivazione automatica delle istanze per la classe
Lift
per Template Haskell.
La classe Lift
, a differenza di altre classi derivabili, vive in template-haskell
invece che in base
. Se un tipo di dati è un'istanza di Lift
permette di promuovere i suoi valori a espressioni Template Haskell (di tipo ExpQ
e Code Q a
), che possono essere inserite nel codice sorgente Haskell.
Ecco un esempio di come si può derivare Lift
:
{-# LANGUAGE DeriveLift #-} module Bar where import Language.Haskell.TH.Syntax data Foo a = Foo a | a :^: a deriving Lift {- instance (Lift a) => Lift (Foo a) where lift (Foo a) = [| Foo a |] lift ((:^:) u v) = [| (:^:) u v |] liftTyped (Foo a) = [|| Foo a ||] liftTyped ((:^:) u v) = [|| (:^:) u v ||] -} ----- {-# LANGUAGE TemplateHaskell #-} module Baz where import Bar import Language.Haskell.TH.Lift foo :: Foo String foo = $(lift $ Foo "foo") fooExp :: Lift a => Foo a -> Q Exp fooExp f = [| f |]
Si noti che l'elemento Lift
sfrutta il vantaggio di polimorfismo Levity per supportare istanze che coinvolgono tipi non inscatolati. Ciò significa che DeriveLift
funziona anche per questi tipi:
{-# LANGUAGE DeriveLift, MagicHash #-} module Unboxed where import GHC.Exts import Language.Haskell.TH.Syntax data IntHash = IntHash Int# deriving Lift {- instance Lift IntHash where lift (IntHash i) = [| IntHash i |] liftTyped (IntHash i) = [|| IntHash i ||] -}