Risposte:
L'uso Maybe
(o suo cugino Either
che funziona sostanzialmente allo stesso modo ma consente di restituire un valore arbitrario al posto di Nothing
) ha uno scopo leggermente diverso dalle eccezioni. In termini Java, è come avere un'eccezione controllata piuttosto che un'eccezione di runtime. Rappresenta qualcosa di previsto che devi affrontare, piuttosto che un errore che non ti aspettavi.
Quindi una funzione come indexOf
restituirebbe un Maybe
valore perché ci si aspetta la possibilità che l'elemento non sia nell'elenco. Questo è molto simile al ritorno null
da una funzione, tranne che in un modo sicuro che ti costringe ad affrontare il null
caso. Either
funziona allo stesso modo tranne per il fatto che è possibile restituire 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 usando Either
quella che genera un'eccezione. Nel caso dell'eccezione, l'unica vera risorsa è una try...catch
dichiarazione. Per la Either
funzione, è possibile utilizzare combinatori esistenti per rendere più chiaro il controllo del flusso. Qui ci sono un paio di esempi:
Innanzitutto, supponiamo che tu voglia provare diverse funzioni che potrebbero sbagliarsi di seguito fino a quando non ne ottieni una che non funziona. Se non ne ricevi nessuno senza errori, desideri restituire un messaggio di errore speciale. Questo è in realtà un modello molto utile, ma sarebbe un dolore orribile usando try...catch
. Fortunatamente, poiché 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 è avere una funzione opzionale. Supponiamo che tu abbia diverse funzioni da eseguire, inclusa una che tenta di ottimizzare una query. In caso contrario, si desidera comunque eseguire tutto il resto. Puoi scrivere un codice simile a:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Entrambi questi casi sono più chiari e più brevi dell'uso try..catch
e, soprattutto, più semantici. L'uso di una funzione come <|>
o optional
rende le tue intenzioni molto più chiare rispetto all'utilizzo try...catch
per gestire sempre le eccezioni.
Si noti inoltre che non è necessario sporcare il codice con righe come if a == Nothing then Nothing else ...
! Il punto centrale del trattamento Maybe
e Either
come monade è evitare questo. È possibile codificare la semantica di propagazione nella funzione bind in modo da ottenere gratuitamente i controlli null / error. L'unica volta che devi controllare esplicitamente è se vuoi restituire qualcosa di diverso da Nothing
un dato Nothing
, e anche così è facile: ci sono un sacco di funzioni di libreria standard per rendere quel codice più piacevole.
Infine, un altro vantaggio è che un Maybe
/ Either
tipo è solo più semplice. Non è necessario estendere la lingua con parole chiave o strutture di controllo aggiuntive: tutto è solo una libreria. Dato che sono solo valori normali, rende il sistema dei tipi più semplice - in Java, devi differenziare i tipi (ad esempio il tipo di ritorno) e gli effetti (ad esempio le throws
dichiarazioni) dove non utilizzeresti Maybe
. Si comportano anche come qualsiasi altro tipo definito dall'utente: non è necessario disporre di un codice di gestione degli errori speciale inserito nella lingua.
Un'altra vittoria è che Maybe
/ Either
sono funzionali e monadi, il che significa che possono trarre vantaggio dalle funzioni di flusso di controllo delle monadi esistenti (di cui esiste un numero equo) e, in generale, giocare bene insieme ad altre monadi.
Detto questo, ci sono alcuni avvertimenti. Per uno, Maybe
né Either
sostituire né le eccezioni non controllate . Avrai bisogno di un altro modo per gestire cose come la divisione per 0 semplicemente perché sarebbe un dolore avere ogni singola divisione restituire un Maybe
valore.
Un altro problema è la restituzione di più tipi di errori (vale solo per Either
). Con le eccezioni, è possibile generare qualsiasi tipo diverso di eccezioni nella stessa funzione. con Either
, ottieni solo un tipo. Questo può essere superato con la sotto-digitazione o un ADT contenente tutti i diversi tipi di errori come costruttori (questo secondo approccio è quello che viene solitamente utilizzato in Haskell).
Soprattutto, preferisco l' approccio Maybe
/ Either
perché lo trovo più semplice e flessibile.
OpenFile()
può lanciare FileNotFound
o NoPermission
o TooManyDescriptors
ecc. A Nessuno non contiene queste informazioni.if None return None
dichiarazioni di stile.Soprattutto, un'eccezione e una forse monade hanno scopi diversi: un'eccezione viene utilizzata per indicare un problema, mentre forse non lo è.
"Infermiera, se c'è un paziente nella stanza 5, puoi chiedergli di aspettare?"
(nota il "se" - questo significa che il dottore si aspetta una monade Maybe)
None
valori possono essere semplicemente propagati). Il tuo punto 5 è solo giusto ... la domanda è: quali situazioni sono inequivocabilmente eccezionali? A quanto pare ... non molti .
bind
in modo tale che il test None
non comporti comunque un sovraccarico sintattico. Un esempio molto semplice, C # sovraccarica gli Nullable
operatori in modo appropriato. Nessun controllo None
necessario, anche quando si utilizza il tipo. Ovviamente il controllo è ancora fatto (è sicuro per il tipo), ma dietro le quinte e non ingombra il codice. Lo stesso vale in un certo senso per la tua obiezione alla mia obiezione a (5), ma sono d'accordo che potrebbe non essere sempre applicabile.
Maybe
come una monade è di rendere None
implicita la propagazione . Ciò significa che se si desidera restituire un None
dato None
, non è necessario scrivere alcun codice speciale. L'unica volta che devi abbinare è se vuoi fare qualcosa di speciale None
. Non hai mai bisogno di una if None then None
sorta di affermazioni.
null
controllando esattamente così (es. if Nothing then Nothing
) Gratuitamente perché Maybe
è una monade. È codificato nella definizione di bind ( >>=
) per Maybe
.
Either
) Che si comportano proprio come Maybe
. Il passaggio tra i due è in realtà piuttosto semplice perché Maybe
è davvero solo un caso speciale di Either
. (A Haskell, potresti pensare a Maybe
come Either ()
.)
"Forse" non sostituisce le eccezioni. Le eccezioni sono pensate per essere utilizzate in casi eccezionali (ad esempio: l'apertura di una connessione db e il server db non è presente sebbene dovrebbe esserlo). "Forse" è per modellare una situazione in cui potresti avere o meno un valore valido; supponiamo che tu stia ottenendo un valore da un dizionario per una chiave: potrebbe essere lì o potrebbe non esserlo - non c'è nulla di "eccezionale" in nessuno di questi risultati.
Secondo la risposta di Tikhon, ma penso che ci sia un punto pratico molto importante che manca a tutti:
Either
meccanismo non è affatto accoppiato ai thread.Quindi qualcosa che vediamo oggi nella vita reale è che molte soluzioni di programmazione asincrona stanno adottando una variante dello Either
stile di gestione degli errori. Considera le promesse di Javascript , come dettagliato in uno di questi link:
Il concetto di promesse ti consente di scrivere codice asincrono come questo (preso dall'ultimo link):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Fondamentalmente, una promessa è un oggetto che:
Fondamentalmente, poiché il supporto delle eccezioni nativo del linguaggio non funziona quando il calcolo avviene su più thread, un'implementazione promettente deve fornire un meccanismo di gestione degli errori, e questi si rivelano essere monadi simili ai tipi Maybe
/ di Haskell Either
.
Il sistema di tipi Haskell richiederà all'utente di riconoscere la possibilità di a Nothing
, mentre i linguaggi di programmazione spesso non richiedono che venga rilevata un'eccezione. Ciò significa che sapremo, al momento della compilazione, che l'utente ha verificato la presenza di un errore.
throws NPE
a ogni singola firma e catch(...) {throw ...}
a ogni singolo corpo del metodo. Ma credo che ci sia un mercato per il check-in nello stesso senso di Forse: il nullability è facoltativo e monitorato nel sistema dei tipi.
La monade forse è fondamentalmente la stessa dell'uso della maggior parte del linguaggio tradizionale del controllo "null significa errore" (tranne per il fatto che richiede il controllo null) e presenta in gran parte gli stessi vantaggi e svantaggi.
Maybe
numeri scrivendo a + b
senza la necessità di verificare None
e il risultato è ancora una volta un valore opzionale.
Maybe
tipo , ma l'uso Maybe
come monade aggiunge zucchero di sintassi che consente di esprimere la logica null-ish in modo molto più elegante.
La gestione delle eccezioni può essere una vera seccatura per il factoring e il testing. So che Python fornisce una bella sintassi "con" che ti permette di intercettare le eccezioni senza il rigido blocco "prova ... cattura". Ma in Java, ad esempio, provare a catturare i blocchi sono grandi, piatti, verbosi o estremamente dettagliati e difficili da spezzare. Inoltre, Java aggiunge tutto il rumore attorno alle eccezioni verificate e non selezionate.
Se, invece, la tua monade rileva le eccezioni e le tratta come una proprietà dello spazio monadico (invece di qualche anomalia di elaborazione), allora sei libero di mescolare e abbinare le funzioni che leghi in quello spazio indipendentemente da ciò che lanciano o catturano.
Se, meglio ancora, la tua monade impedisce condizioni in cui potrebbero verificarsi eccezioni (come spingere un segno di spunta nullo in Forse), allora anche meglio. se ... allora è molto, molto più facile da valutare e testare che provare ... catturare.
Da quello che ho visto Go sta adottando un approccio simile specificando che ogni funzione ritorna (risposta, errore). È un po 'come "sollevare" la funzione in uno spazio monade in cui il tipo di risposta principale è decorato con un'indicazione di errore ed efficacemente lanciare e catturare eccezioni.