Costruttori con argomenti
I costruttori di un nuovo tipo di dato possono avere argomenti che consentono di associare informazioni aggiuntive al valore che viene costruito.
Funzioni parziali e totali
Consideriamo il problema di definire una funzione elemento
che,
applicata a un indice xs
,
restituisca l’elemento con indice xs
. Nel definire il tipo
della funzione elemento
occorre prendere in considerazione
l’eventualità che l’indice
Possiamo definire elemento
come una funzione parziale, ovvero
che non è definita per tutti i valori possibili dei suoi argomenti:
L’applicazione di indice
a un indice non valido causa
un’eccezione, in quanto non c’è una equazione che definisca il
comportamento di elemento
in questo caso:
Se si vuole definire “elemento” come una funzione totale, quindi definita per tutti i valori dei suoi argomenti, una strategia possibile è quella di usare un numero particolare da restituire quando l’indice non è valido:
Questa soluzione ha due difetti gravi. In primo luogo, si
“sacrifica” un valore specifico del codominio (-1
) per indicare
una condizione di errore. Questo significa che, ottenendo come
risultato -1
, non è possibile distinguere il caso in cui l’indice
fornito non è valido da quello in cui l’indice è valido e l’elemento
che si trova in quella posizione è proprio -1
. Il secondo difetto,
legato al primo, è che l’utilizzatore della funzione elemento
può
dimenticarsi di controllare se il valore restituito dalla funzione è
quello che segnala l’errore, dal momento che questo valore è
indistinguibile da quello di un valore “normale”.
Lo scenario ideale è quello in cui la funzione elemento
restituisca un valore di un nuovo tipo di dato distinto da Int
e
i cui valori hanno due forme possibili a seconda che l’indice
fornito sia valido oppure no. A questo scopo una enumerazione non basta, perché
nel caso in cui l’indice sia valido è necessario anche comunicare
l’elemento che si trova in quella posizione.
Costruttori come funzioni
È possibile dotare i costruttori di un nuovo tipo di dato di
argomenti per associare informazioni ausiliarie ai valori
costruiti. Nel caso della funzione elemento
, possiamo concepire un
tipo di dato ForseInt
con la seguente struttura:
I valori di tipo ForseInt
hanno due forme possibili
corrispondenti ai costruttori Niente
e Proprio
. Quest’ultimo ha
un argomento di tipo Int
che possiamo utilizzare per indicare
esplicitamente quale elemento della lista è stato trovato. In
altri termini, mentre Niente
è da solo un valore di tipo
ForseInt
, Proprio
è una funzione che, applicata a un numero
intero, produce un valore di tipo ForseInt
:
Usando ForseInt
, possiamo ridefinire elemento
come funzione
totale nel seguente modo:
Pattern matching sul risultato di una funzione
Supponiamo ora di voler definire una funzione posizione :: Int ->
[Int] -> ForseInt
che, applicata a un numero x
e a una lista
xs
, restituisca l’indice della prima occorrenza di x
in
xs
. Possiamo sfruttare il tipo di dato ForseInt
anche per
definire posizione
come una funzione totale che restituisce
Niente
nel caso in cui x
non è presente in xs
. Il
comportamento di posizione
è semplice da descrivere a parole:
- se
xs
è la lista è vuota, il risultato èNiente
; - se
x
è il primo elemento della lista, il risultato èProprio 0
; - altrimenti, il risultato è rispettivamente
Niente
oProprio (n + 1)
a seconda chex
non sia presente nella coda della lista o sia presente nella coda della lista all’indicen
.
La realizzazione dell’ultimo caso in codice Haskell richiede la risoluzione di un problema tecnico che non si è mai presentato fino ad ora, ovvero il pattern matching sul risultato dell’applicazione di una funzione invece che su uno dei suoi argomenti. Questo pattern matching è realizzabile per mezzo di una case expression come mostrato nel codice che segue:
L’espressione case E of...
consente di confrontare il valore di
E
, nel caso specifico il risultato dell’applicazione ricorsiva
posizione x xs
, con i pattern che seguono of
. A destra del
simbolo ->
è specificato il risultato dell’espressione nei vari
casi.
Esercizi
- Realizzare una funzione totale
testa :: [Int] -> ForseInt
che, applicata a una listaxs
, restituisca la testa dixs
sexs
non è vuota, eNiente
altrimenti. - Definire un tipo di dato
Numero
i cui valori hanno la formaI n
doven
è un numero intero oF f
dovef
è un numero floating-point a precisione singola. Questo tipo di dato può essere usato per rappresentare in modo uniforme numeri di tipo diverso. Definire una funzionesomma :: Numero -> Numero -> Numero
per sommare due numeri in modo tale che il risultato sia floating-point solo se necessario. - Usando la funzione
somma
dell’esercizio precedente ma senza usare esplicitamente la ricorsione, definire una funzionesommatoria :: [Numero] -> Numero
che, applicata a una lista di numeri, ne calcoli la somma. - Definire una funzione
proprio :: [ForseInt] -> [Int]
che, applicata a una listaxs
di valori di tipoForseInt
, restituisca la lista dei valorin
tali cheProprio n
compare inxs
.