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 classi Generic e Generic1, definite in GHC.Generics. Si possono usare per definire funzioni generiche, come descritto in Programmazione generica.
  • Con DeriveFunctorè possibile derivare istanze della classe Functordefinita in GHC.Base.
  • Con DeriveDataTypeableè possibile derivare istanze della classe Datadefinita in Data.Data.
  • Con DeriveFoldableè possibile derivare istanze della classe Foldabledefinita in Data.Foldable.
  • Con DeriveTraversableè possibile derivare istanze della classe Traversabledefinita in Data.Traversable. Poiché la classe Traversable detta le istanze di Functor e Foldableprobabilmente si vorrà derivare anche loro, quindi DeriveTraversable implica DeriveFunctor e DeriveFoldable.
  • Con DeriveLiftè possibile derivare istanze della classe Lift, definita nella classe Language.Haskell.TH.Syntax del modulo template-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 DeriveFunctorsi 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 ae, 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 aGHC deve produrre un valore di tipo Right b. Poiché l'argomento del metodo Right ha il tipo Either Int ail codice chiama ricorsivamente fmap per produrre un valore di tipo Either Int bche 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 Wrongche 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:

  1. 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 da data Triple a = Triple a Int [a]ma con un macchinario aggiuntivo per gestire le tuple.

  2. 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), allora a è in posizione contravariante e b è in posizione covariante. Allo stesso modo, se a -> b appare in una posizione contravariante (ad es, CovFun2 sopra), allora a si trova in a posizione covariante e b è 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 dati Functor ContraFun1 esista. L'implementazione sarebbe simile a questa:

    instance Functor ContraFun1 where
      fmap f (ContraFun g) = ContraFun (x -> _)
    

    Abbiamo f :: a -> b, g :: a -> Inte x :: b. Utilizzando questi, dobbiamo in qualche modo riempire il buco (indicato con un trattino basso) con un valore del tipo Int. Quali sono le nostre opzioni?

    Possiamo provare ad applicare g a x. Questo però non funzionerà, perché g si aspetta un argomento di tipo ae x :: b. Ancora peggio, non possiamo trasformare x in qualcosa di tipo a, poiché f ha bisogno di un argomento di tipo a! In breve, non c'è un buon modo per farlo funzionare.

    D'altra parte, una funzione derivata Functor per la classe CovFunrientrano 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:

  1. Un tipo di dati non ha parametri di tipo (per esempio, data Nothing = Nothing).
  2. 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).
  3. 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 DeriveFoldablesi 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, foldre 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 foldMape False per null.

  1. Quando un tipo che non è sintatticamente equivalente a ama che contiene a, si incontra un tipo che non è sintatticamente equivalente a a , ma che contiene a , DeriveFunctor chiama ricorsivamente fmap su di esso. Allo stesso modo, DeriveFoldable chiamerebbe ricorsivamente foldr e foldMap. A seconda del contesto, null può chiamare ricorsivamente null o all null. Ad esempio, dato

    data 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
    
  2. DeriveFunctor rimette tutto insieme alla fine, invocando il costruttore. DeriveFoldableinvece, costruisce un valore di qualche tipo. Per foldrquesto si ottiene concatenando le applicazioni di f e ricorsivo foldr sul valore di stato z. Per foldMapQuesto avviene combinando tutti i valori con mappend. Per null, i valori sono solitamente combinati con &&. Tuttavia, se uno dei valori è noto per essere False, 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:

  1. I tipi di dati che contengono tipi di funzione sul lato destro non possono avere istanze derivate Foldable . Foldable istanze.
  2. 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 Foldableistanza:

    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 di E1 è 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 o E4 perché anche se (a ~ Int), Int non è sintatticamente equivalente a a.
  • Non ripieghiamo sull'argomento di E3 perché a non è universalmente polimorfo. Il ain E3 è (implicitamente) quantificato esistenzialmente, quindi non è lo stesso dell'ultimo parametro di tipo di E.

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 DeriveTraversablesi 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:

  1. Quando una variabile di tipo nudo avengono incontrate entrambe le variabili DeriveFunctor e DeriveTraversable genererebbero f a per un fmap e traverse rispettivamente.
  2. Quando un tipo che non è sintatticamente equivalente a ama che contiene a, si incontra un tipo che non è sintatticamente equivalente a a, ma che contiene a, DeriveFunctor chiama ricorsivamente fmap su di esso. Allo stesso modo, DeriveTraversable chiamerebbe ricorsivamente traverse.
  3. DeriveFunctor e alla fine rimette tutto insieme invocando il costruttore. DeriveTraversable fa qualcosa di simile, ma funziona in modo Applicative 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, DeriveFoldablee 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 Typeablee 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'elemento DeriveDataTypeable 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 ||]
-}