Forse monade vs eccezioni

30

Mi chiedo quali sono i vantaggi di Maybe monad rispetto alle eccezioni? Sembra che Maybe sia solo un modo esplicito (e piuttosto dispendioso) di try..catch sintassi.

aggiornamento Tieni presente che io non intenzionalmente menziono Haskell.

    
posta Vladimir 30.05.2012 - 15:01
fonte

7 risposte

54

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é MaybeEither 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.

    
risposta data 30.05.2012 - 22:54
fonte
9
  1. Un'eccezione può contenere più informazioni sulla fonte di un problema. OpenFile() può lanciare FileNotFound o NoPermission o TooManyDescriptors ecc. A Nessuno non contiene queste informazioni.
  2. Le eccezioni possono essere utilizzate in contesti che non hanno valori di ritorno (ad esempio con costruttori in lingue che li hanno).
  3. Un'eccezione ti consente di inviare facilmente le informazioni nello stack, senza un sacco di istruzioni di if None return None -style.
  4. La gestione delle eccezioni ha quasi sempre un impatto sulle prestazioni più elevato rispetto alla semplice restituzione di un valore.
  5. Soprattutto, un'eccezione e una Forse monad hanno scopi diversi - un'eccezione è usata per indicare un problema, mentre un Forse non lo è.

    "Infermiere, se c'è un paziente nella stanza 5, puoi chiedergli di aspettare?"

    • Forse monade: "Dottore, non c'è nessun paziente nella stanza 5".
    • Eccezione: "Dottore, non c'è spazio 5!"

    (nota il "se" - questo significa che il dottore sta aspettandosi un Forse monade)

risposta data 30.05.2012 - 16:07
fonte
6

"Forse" non è un sostituto per le eccezioni. Le eccezioni sono pensate per essere utilizzate in casi eccezionali (ad esempio: apertura di una connessione db e il server db non è lì, anche se dovrebbe esserlo). "Forse" è per modellare una situazione quando si può o non si può avere un valore valido; diciamo che stai ricevendo un valore da un dizionario per una chiave: potrebbe esserci o non esserlo, non c'è nulla di "eccezionale" in nessuno di questi risultati.

    
risposta data 30.05.2012 - 15:48
fonte
6

In secondo luogo la risposta di Tikhon, ma penso che ci sia un punto pratico molto importante a cui tutti mancano:

  1. I meccanismi di gestione delle eccezioni, almeno nelle lingue tradizionali, sono intimamente accoppiati ai singoli thread.
  2. Il meccanismo Either non è associato ai thread.

Quindi qualcosa che stiamo vedendo oggigiorno nella realtà è che molte soluzioni di programmazione asincrona stanno adottando una variante dello stile Either della gestione degli errori. Considera JavaScript promises , come dettagliato in uno di questi link:

Il concetto di promesse ti permette di scrivere un codice asincrono come questo (tratto 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’
    });

In sostanza, una promessa è un oggetto che:

  1. Rappresenta il risultato di un calcolo asincrono, che potrebbe essere o non essere stato ancora completato;
  2. Consente di concatenare ulteriori operazioni da eseguire sul risultato, che verranno attivate quando tale risultato sarà disponibile e i cui risultati a turno sono disponibili come promesse;
  3. Ti permette di collegare un gestore di errori che verrà invocato se il calcolo della promessa fallisce. Se non c'è alcun gestore, l'errore viene propagato ai successivi gestori della catena.

Sostanzialmente, poiché il supporto delle eccezioni native della lingua non funziona quando il calcolo avviene su più thread, un'implementazione promessa deve fornire un meccanismo di gestione degli errori e queste risultano monad simili a% has_cart di Haskell / Maybe types.

    
risposta data 15.09.2015 - 02:53
fonte
1

Il sistema di tipi Haskell richiederà all'utente di riconoscere la possibilità di un Nothing , mentre i linguaggi di programmazione spesso non richiedono che venga rilevata un'eccezione. Ciò significa che sapremo, in fase di compilazione, che l'utente ha verificato un errore.

    
risposta data 30.05.2012 - 15:14
fonte
0

Forse la monade è fondamentalmente la stessa della maggior parte dell'uso tradizionale del controllo "null means error" (eccetto che richiede il null da verificare), e ha in gran parte gli stessi vantaggi e svantaggi.

    
risposta data 30.05.2012 - 15:12
fonte
0

La gestione delle eccezioni può essere un vero problema per il factoring e il testing. So python fornisce una sintassi "con" che ti permette di intercettare le eccezioni senza il rigido blocco "try ... catch". Ma in Java, ad esempio, prova a catturare i blocchi: sono grandi, lievi, verbosi o estremamente prolissi e difficili da separare. Inoltre, Java aggiunge tutto il rumore attorno alle eccezioni controllate e deselezionate.

Se, invece, la tua monade rileva le eccezioni e le tratta come una proprietà dello spazio monadico (invece di un'anomalia di elaborazione), sei libero di mescolare e abbinare le funzioni che ti legano a quello spazio indipendentemente da ciò che lanciano o cattura.

Se, ancora meglio, la tua monade impedisce condizioni in cui potrebbero verificarsi eccezioni (come spingere un assegno nullo in Forse), allora ancora meglio. se ... allora è molto, molto più facile da calcolare e provare che provare ... catturare.

Da quello che ho visto Go sta adottando un approccio simile specificando che ciascuna funzione restituisce (risposta, errore). Questo è lo stesso di "sollevamento" della funzione in uno spazio di monade in cui il tipo di risposta di base è decorato con un'indicazione di errore ed efficacemente il lancio laterale e l'ampli; rilevando eccezioni.

    
risposta data 16.02.2014 - 09:09
fonte