In una data
dichiarazione, un costruttore di tipo è l'elemento a sinistra del segno di uguale. I costruttori di dati sono le cose a destra del segno di uguale. Si utilizzano costruttori di tipi in cui è previsto un tipo e si utilizzano costruttori di dati in cui è previsto un valore.
Costruttori di dati
Per rendere le cose semplici, possiamo iniziare con un esempio di un tipo che rappresenta un colore.
data Colour = Red | Green | Blue
Qui abbiamo tre costruttori di dati. Colour
è un tipo ed Green
è un costruttore che contiene un valore di tipo Colour
. Allo stesso modo, Red
e Blue
sono entrambi costruttori che costruiscono valori di tipo Colour
. Potremmo immaginare di aromatizzarlo però!
data Colour = RGB Int Int Int
Abbiamo ancora solo il tipo Colour
, ma RGB
non è un valore: è una funzione che prende tre Ints e restituisce un valore! RGB
ha il tipo
RGB :: Int -> Int -> Int -> Colour
RGB
è un costruttore di dati che è una funzione che prende alcuni valori come argomenti e quindi li usa per costruire un nuovo valore. Se hai eseguito una programmazione orientata agli oggetti, dovresti riconoscerlo. In OOP, i costruttori accettano anche alcuni valori come argomenti e restituiscono un nuovo valore!
In questo caso, se applichiamo RGB
a tre valori, otteniamo un valore di colore!
Prelude> RGB 12 92 27
#0c5c1b
Abbiamo costruito un valore di tipo Colour
applicando il costruttore di dati. Un costruttore di dati contiene un valore come farebbe una variabile oppure prende altri valori come argomento e crea un nuovo valore . Se hai programmato in precedenza, questo concetto non dovrebbe essere molto strano per te.
Intervallo
Se desideri costruire un albero binario per memorizzare String
i messaggi di posta elettronica , potresti immaginare di fare qualcosa di simile
data SBTree = Leaf String
| Branch String SBTree SBTree
Quello che vediamo qui è un tipo SBTree
che contiene due costruttori di dati. In altre parole, ci sono due funzioni (vale a dire Leaf
e Branch
) che costruiranno valori del SBTree
tipo. Se non hai familiarità con il funzionamento degli alberi binari, resta lì. In realtà non è necessario sapere come funzionano gli alberi binari, solo che questo memorizza String
i messaggi di posta elettronica in qualche modo.
Vediamo anche che entrambi i costruttori di dati accettano un String
argomento: questa è la stringa che memorizzeranno nell'albero.
Ma! E se volessimo anche essere in grado di memorizzare Bool
, dovremmo creare un nuovo albero binario. Potrebbe assomigliare a questo:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Tipo costruttori
Entrambi SBTree
e BBTree
sono costruttori di tipi. Ma c'è un problema evidente. Vedi quanto sono simili? Questo è un segno che vuoi davvero un parametro da qualche parte.
Quindi possiamo farlo:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Ora introduciamo una variabile di tipo a
come parametro nel costruttore del tipo. In questa dichiarazione, BTree
è diventata una funzione. Accetta un tipo come argomento e restituisce un nuovo tipo .
E 'importante prendere in considerazione la differenza tra un tipo concreto (esempi includono Int
, [Char]
e Maybe Bool
), che è un tipo che può essere assegnato a un valore nel programma, ed una funzione di tipo costruttore di cui avete bisogno per alimentare un tipo di essere in grado di essere assegnato a un valore. Un valore non può mai essere di tipo "lista", perché deve essere una "lista di qualcosa ". Nello stesso spirito, un valore non può mai essere di tipo "albero binario", perché deve essere un "albero binario che memorizza qualcosa ".
Se passiamo, diciamo, Bool
come argomento a BTree
, restituisce il tipo BTree Bool
, che è un albero binario che memorizza Bool
s. Sostituisci ogni occorrenza della variabile di tipo a
con il tipo Bool
e puoi vedere di persona come è vero.
Se vuoi, puoi visualizzare BTree
come una funzione con il tipo
BTree :: * -> *
I tipi sono un po 'come i tipi: *
indica un tipo concreto, quindi diciamo che BTree
va da un tipo concreto a un tipo concreto.
Avvolgendo
Fai un passo indietro qui e prendi nota delle somiglianze.
I costruttori di dati con parametri sono interessanti se vogliamo leggere variazioni nei nostri valori: inseriamo queste variazioni nei parametri e lasciamo che il tizio che crea il valore decida quali argomenti inseriranno. Allo stesso modo, i costruttori di tipi con parametri sono interessanti se vogliamo leggere variazioni nelle nostre tipologie! Mettiamo queste variazioni come parametri e lasciamo che il ragazzo che crea il tipo decida quali argomenti inserire.
Un caso di studio
Come tratto iniziale qui, possiamo considerare il Maybe a
tipo. La sua definizione è
data Maybe a = Nothing
| Just a
Ecco Maybe
un costruttore di tipi che restituisce un tipo concreto. Just
è un costruttore di dati che restituisce un valore. Nothing
è un costruttore di dati che contiene un valore. Se guardiamo il tipo di Just
, lo vediamo
Just :: a -> Maybe a
In altre parole, Just
prende un valore di tipo a
e restituisce un valore di tipo Maybe a
. Se guardiamo il tipo di Maybe
, lo vediamo
Maybe :: * -> *
In altre parole, Maybe
prende un tipo concreto e restituisce un tipo concreto.
Di nuovo! La differenza tra un tipo concreto e una funzione di costruzione del tipo. Non è possibile creare un elenco di messaggi di posta Maybe
elettronica se si tenta di eseguire
[] :: [Maybe]
riceverai un errore. È tuttavia possibile creare un elenco di Maybe Int
, o Maybe a
. Questo perché Maybe
è una funzione di costruzione del tipo, ma un elenco deve contenere valori di un tipo concreto. Maybe Int
e Maybe a
sono tipi concreti (o, se si desidera, chiamate a funzioni di costruzione di tipi che restituiscono tipi concreti.)
Car
è sia un costruttore di tipi (sul lato sinistro di=
) che un costruttore di dati (sul lato destro). Nel primo esempio, ilCar
costruttore del tipo non accetta argomenti, nel secondo esempio ne richiede tre. In entrambi gli esempi, ilCar
costruttore di dati accetta tre argomenti (ma i tipi di questi argomenti sono in un caso fissi e nell'altro parametrizzati).