Funzioni con guardie
Capita spesso che il valore prodotto da una funzione debba essere calcolato in modo diverso a seconda di qualche proprietà dell’argomento. Per esempio, la funzione che calcola il valore assoluto di un numero $n$ ha due comportamenti possibili, a seconda che $n \geq 0$ oppure $n < 0$. Nel primo caso, la funzione restituisce il valore di $n$ immutato, nel secondo restituisce il negato di $n$. Esistono due modi per esprimere questi comportamenti che dipendono da una condizione, le espressioni condizionali e le definizioni con guardie.
È possibile definire funzioni facendo uso di equazioni multiple, ciascuna accompagnata da una condizione — detta guardia — che specifica per quali valori degli argomenti quella equazione viene applicata. Ad esempio, la funzione “valore assoluto” si può realizzare con guardie nel seguente modo:
assoluto :: Int -> Int
assoluto n | n >= 0 = n
| n < 0 = negate n
Ogni guardia è preceduta dal simbolo |. Haskell usa l’indentazione
del codice per capire che guardie diverse appartengono allo stesso
blocco di codice, pertanto è fondamentale allineare verticalmente i
caratteri |. L’allineamento dei simboli = è puramente estetico e
non è necessario.
Quando si applica una funzione con guardie a un argomento, Haskell
usa la prima equazione (dall’alto verso il basso) la cui guardia ha
valore True per determinare il valore prodotto dalla funzione.
assoluto 3
assoluto (negate 3)
Quando si usa un blocco di guardie esaustive, cioè che coprono
tutte le possibilità, l’ultima guardia svolge una funzione analoga a
quella del ramo else di un’espressione condizionale, in quanto il
suo valore deve essere necessariamente True. Nel caso della
funzione assoluto, ad esempio, è evidente che se la condizione $n
\geq 0$ è falsa, allora la condizione $n < 0$ deve essere vera. Da
questo punto di vista, controllare che effettivamente $n$ sia
negativo nell’ultima equazione è ridondante e dunque la funzione
assoluto si può scrivere anche come
assoluto :: Int -> Int
assoluto n | n >= 0 = n
| otherwise = negate n
in cui la guardia otherwise si applica se la prima (in generale,
tutte quelle precedenti) sono false.
Esercizi
- Definire una funzione
Int -> Intche, applicata a un numero intero $n$, calcoli il successore di $n$ se $n$ è pari e il valore assoluto di $n$ se $n$ è dispari. Risolvere l’esercizio due volte, una prima volta usando un’espressione condizionale e una seconda volta con guardie.-- soluzione con espressione condizionale f :: Int -> Int f n = if pari n then successore n else assoluto n -- soluzione con guardie f :: Int -> Int f n | pari n = successore n | otherwise = assoluto n - Definire una funzione
giorni :: Int -> Intche, applicata a un anno $n$, calcoli il numero di giorni dell’anno $n$ a seconda che $n$ sia un anno bisestile o meno. Si faccia uso della funzionebisestiledefinita nella scheda Funzioni.giorni :: Int -> Int giorni n | bisestile n = 366 | otherwise = 365 -
Valutare in
ghcil’espressioneotherwisee, in base alla risposta ottenuta, rivisitare mentalmente la seconda implementazione di
assolutoper comprenderne il funzionamento.L’espressione
otherwisenon è altro che un nome alternativo per la costanteTrue, dunque può essere usata come “ultima guardia” sempre verificata.