L'utilizzo di Maybe
(o del suo cousin Either
che funziona sostanzialmente allo stesso modo ma ti consente di restituire un valore arbitrario al posto di Nothing
) ha uno scopo leggermente diverso rispetto alle eccezioni. In termini Java, è come avere un'eccezione controllata piuttosto che un'eccezione di runtime. Rappresenta qualcosa di atteso che devi affrontare, piuttosto che un errore che non ti aspettavi.
Quindi una funzione come indexOf
restituirebbe un valore Maybe
perché ti aspetti la possibilità che l'elemento non sia nell'elenco. È molto simile a restituire null
da una funzione, tranne in un modo sicuro per il tipo che ti obbliga ad affrontare il caso null
. Either
funziona allo stesso modo tranne che puoi restituire le informazioni associate al caso di errore, quindi in realtà è più simile a un'eccezione di Maybe
.
Quindi quali sono i vantaggi dell'approccio Maybe
/ Either
? Per uno, è un cittadino di prima classe della lingua. Confrontiamo una funzione utilizzando Either
con una che genera un'eccezione. Per il caso di eccezione, l'unica risorsa reale è un'istruzione try...catch
. Per la funzione Either
, è possibile utilizzare i combinatori esistenti per rendere più chiaro il controllo di flusso. Ecco alcuni esempi:
In primo luogo, supponiamo che tu voglia provare diverse funzioni che potrebbero generare errori in una riga finché non ne ottieni una che non lo fa. Se non si ottiene senza errori, si desidera restituire un messaggio di errore speciale. Questo è in realtà un modello molto utile ma sarebbe un dolore orribile con try...catch
. Fortunatamente, dal momento che Either
è solo un valore normale, puoi utilizzare le funzioni esistenti per rendere il codice molto più chiaro:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Un altro esempio sta avendo una funzione opzionale. Supponiamo che tu abbia diverse funzioni da eseguire, inclusa una che cerca di ottimizzare una query. Se questo non funziona, vuoi che tutto il resto funzioni comunque. Potresti scrivere codice come:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Entrambi questi casi sono più chiari e più brevi rispetto all'utilizzo di try..catch
e, cosa più importante, più semantica. L'utilizzo di una funzione come <|>
o optional
rende le tue intenzioni molto più chiare rispetto all'utilizzo di try...catch
per gestire sempre le eccezioni.
Ricorda inoltre che non devi sparpagliare il tuo codice con linee come if a == Nothing then Nothing else ...
! L'intero punto di trattare Maybe
e Either
come monade è evitare questo. È possibile codificare la semantica della propagazione nella funzione bind in modo da ottenere gratuitamente i controlli nulli / di errore. L'unica volta che devi verificare esplicitamente è se vuoi restituire qualcosa diverso da Nothing
dato un Nothing
, e anche allora è facile: ci sono un sacco di funzioni di libreria standard per rendere quel codice più bello.
Infine, un altro vantaggio è che un tipo Maybe
/ Either
è semplicemente più semplice. Non è necessario estendere la lingua con ulteriori parole chiave o strutture di controllo: tutto è solo una libreria. Poiché sono solo valori normali, rende più semplice il sistema di tipi: in Java, devi distinguere tra tipi (ad es. Il tipo restituito) ed effetti (ad esempio throws
istruzioni) in cui non utilizzeresti Maybe
. Si comportano anche come qualsiasi altro tipo definito dall'utente - non è necessario inserire nella lingua un codice di gestione degli errori speciale.
Un'altra vittoria è che Maybe
/ Either
sono funtori e monadi, il che significa che possono trarre vantaggio dalle funzioni di controllo del flusso monad esistenti (di cui esiste un numero giusto) e, in generale, giocare bene insieme a altre monadi.
Detto questo, ci sono alcuni avvertimenti. Per uno, né Maybe
né Either
sostituiscono le eccezioni non selezionate . Avrai bisogno di un altro modo per gestire cose come la divisione per 0 semplicemente perché sarebbe un problema che ogni singola divisione restituisca un valore di Maybe
.
Un altro problema è il ritorno di più tipi di errori (questo si applica solo a Either
). Con le eccezioni, puoi lanciare qualsiasi diverso tipo di eccezione nella stessa funzione. con Either
, ottieni solo un tipo. Questo può essere risolto con la sottotipizzazione o con un ADT contenente tutti i diversi tipi di errori come costruttori (questo secondo approccio è ciò che viene solitamente utilizzato in Haskell).
Ancora, soprattutto, preferisco l'approccio Maybe
/ Either
perché lo trovo più semplice e flessibile.