In una datadichiarazione, 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, Rede Bluesono 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 RGBnon è un valore: è una funzione che prende tre Ints e restituisce un valore! RGBha 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 RGBa tre valori, otteniamo un valore di colore!
Prelude> RGB 12 92 27
#0c5c1b
Abbiamo costruito un valore di tipo Colourapplicando 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 Stringi 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 SBTreeche contiene due costruttori di dati. In altre parole, ci sono due funzioni (vale a dire Leafe Branch) che costruiranno valori del SBTreetipo. 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 Stringi messaggi di posta elettronica in qualche modo.
Vediamo anche che entrambi i costruttori di dati accettano un Stringargomento: 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 SBTreee BBTreesono 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, Boolcome argomento a BTree, restituisce il tipo BTree Bool, che è un albero binario che memorizza Bools. Sostituisci ogni occorrenza della variabile di tipo acon il tipo Boole puoi vedere di persona come è vero.
Se vuoi, puoi visualizzare BTreecome una funzione con il tipo
BTree :: * -> *
I tipi sono un po 'come i tipi: *indica un tipo concreto, quindi diciamo che BTreeva 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 atipo. La sua definizione è
data Maybe a = Nothing
| Just a
Ecco Maybeun 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, Justprende un valore di tipo ae restituisce un valore di tipo Maybe a. Se guardiamo il tipo di Maybe, lo vediamo
Maybe :: * -> *
In altre parole, Maybeprende 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 Maybeelettronica 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 Inte Maybe asono 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, ilCarcostruttore del tipo non accetta argomenti, nel secondo esempio ne richiede tre. In entrambi gli esempi, ilCarcostruttore di dati accetta tre argomenti (ma i tipi di questi argomenti sono in un caso fissi e nell'altro parametrizzati).