Risposte:
L'uso Maybe(o suo cugino Eitherche 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 indexOfrestituirebbe un Maybevalore perché ci si aspetta la possibilità che l'elemento non sia nell'elenco. Questo è molto simile al ritorno nullda una funzione, tranne che in un modo sicuro che ti costringe ad affrontare il nullcaso. Eitherfunziona 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 Eitherquella che genera un'eccezione. Nel caso dell'eccezione, l'unica vera risorsa è una try...catchdichiarazione. Per la Eitherfunzione, è 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..catche, soprattutto, più semantici. L'uso di una funzione come <|>o optionalrende le tue intenzioni molto più chiare rispetto all'utilizzo try...catchper 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 Maybee Eithercome 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 Nothingun 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/ Eithertipo è 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 throwsdichiarazioni) 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/ Eithersono 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, Maybené Eithersostituire 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 Maybevalore.
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/ Eitherperché lo trovo più semplice e flessibile.
OpenFile()può lanciare FileNotFoundo NoPermissiono TooManyDescriptorsecc. A Nessuno non contiene queste informazioni.if None return Nonedichiarazioni 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)
Nonevalori possono essere semplicemente propagati). Il tuo punto 5 è solo giusto ... la domanda è: quali situazioni sono inequivocabilmente eccezionali? A quanto pare ... non molti .
bindin modo tale che il test Nonenon comporti comunque un sovraccarico sintattico. Un esempio molto semplice, C # sovraccarica gli Nullableoperatori in modo appropriato. Nessun controllo Nonenecessario, 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.
Maybecome una monade è di rendere Noneimplicita la propagazione . Ciò significa che se si desidera restituire un Nonedato 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 Nonesorta di affermazioni.
nullcontrollando 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 Maybecome 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:
Eithermeccanismo non è affatto accoppiato ai thread.Quindi qualcosa che vediamo oggi nella vita reale è che molte soluzioni di programmazione asincrona stanno adottando una variante dello Eitherstile 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 NPEa 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.
Maybenumeri scrivendo a + b senza la necessità di verificare Nonee il risultato è ancora una volta un valore opzionale.
Maybe tipo , ma l'uso Maybecome 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.