Il modo più semplice per segnalare errori in Haskell

22

Sto lavorando per imparare Haskell e ho scoperto tre diversi modi di affrontare gli errori nelle funzioni che scrivo:

  1. Posso semplicemente scrivere error "Some error message." , che genera un'eccezione.
  2. Posso avere la mia funzione restituire Maybe SomeType , in cui potrei o non riuscire a restituire ciò che mi piacerebbe restituire.
  3. Posso avere la mia funzione restituire Either String SomeType , in cui posso restituire un messaggio di errore o quello che mi è stato chiesto di restituire in primo luogo.

La mia domanda è: Quale metodo di gestione degli errori dovrei usare e perché? Forse dovrei usare metodi diversi, a seconda del contesto?

La mia attuale comprensione è:

  • È "difficile" gestire le eccezioni nel codice puramente funzionale, e in Haskell si desidera mantenere le cose il più puramente funzionali possibili.
  • Il ritorno di Maybe SomeType è la cosa giusta da fare se la funzione fallirà o avrà successo (cioè non ci sono modi in cui può fallire ).
  • Il ritorno di Either String SomeType è la cosa giusta da fare se una funzione può fallire in uno qualsiasi dei vari modi.
posta CmdrMoozy 11.08.2014 - 21:30
fonte

2 risposte

31

OK, prima regola della gestione degli errori in Haskell: Non utilizzare mai error .

È semplicemente terribile in ogni modo. Esiste puramente come un atto storico e il fatto che Prelude lo usi è terribile. Non usarlo.

L'unico momento immaginabile che potresti usare è quando qualcosa è così internamente terribile che qualcosa deve essere sbagliato con la trama stessa della realtà, rendendo così l'esito del tuo programma discutibile.

Ora la domanda diventa Maybe rispetto a Either . Maybe è particolarmente adatto a qualcosa come head , che può o non può restituire un valore ma c'è solo una possibile ragione per fallire. Nothing sta dicendo qualcosa come "è rotto, e sai già perché". Alcuni direbbero che indica una funzione parziale.

La forma più affidabile di gestione degli errori è Either + un errore ADT.

Ad esempio in uno dei miei compilatori di hobby, ho qualcosa di simile a

data CompilerError = ParserError ParserError
                   | TCError TCError
                   ...
                   | ImpossibleError String

data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
             | MissingDefinition Name
             | InfiniteType Ty
             ...

type ErrorM m = ExceptT CompilerError m -- from MTL

Ora definisco un gruppo di tipi di errore, annidandoli in modo da avere un glorioso errore di primo livello. Questo può essere un errore da qualsiasi fase della compilazione o un ImpossibleError , che indica un errore del compilatore.

Ognuno di questi tipi di errore cerca di conservare quante più informazioni possibili per una buona stampa o altre analisi. Ancora più importante, non avendo una stringa, posso verificare che l'esecuzione di un programma illtyped tramite il controllo di tipo genera effettivamente un errore di unificazione! Una volta che qualcosa è un String , è sparito per sempre e qualsiasi informazione contenuta è opaca per il compilatore / test, quindi anche Either String non è neanche eccezionale.

Finalmente impacchetto questo tipo in ExceptT , un nuovo trasformatore monad da MTL. Questo è essenzialmente EitherT e viene fornito con un bel gruppo di funzioni per lanciare e catturare errori in modo puro e piacevole.

Infine, vale la pena ricordare che Haskell ha i meccanismi per supportare la gestione delle eccezioni come fanno gli altri linguaggi, eccetto che il rilevamento di un'eccezione vive in IO . So che alcune persone amano usare queste applicazioni per IO pesanti dove tutto potrebbe potenzialmente fallire, ma così raramente che a loro non piace pensarci. Se si utilizzano queste eccezioni impure o solo ExceptT Error IO è davvero una questione di gusti. Personalmente opto per ExceptT perché mi piace essere ricordato la possibilità di un fallimento.

Come riepilogo,

  • Maybe - Posso fallire in un modo ovvio
  • Either CustomType - Posso fallire e ti dirò cosa è successo
  • IO + eccezioni: a volte non riesco. Controlla i miei documenti per vedere cosa lancio quando faccio
  • error - Ti odio anche tu utente
risposta data 11.08.2014 - 21:53
fonte
2

Non separerei 2 e 3 in base a quanti modi può fallire qualcosa. Non appena pensi che ci sia solo un modo possibile, un altro mostrerà la sua faccia. La metrica dovrebbe invece essere "fai attenzione ai miei chiamanti perché qualcosa è fallito? Possono davvero fare qualcosa al riguardo?".

Oltre a ciò, non mi è immediatamente chiaro che Either String SomeType può produrre una condizione di errore. Vorrei creare un semplice tipo di dati algebrico con le condizioni con un nome più descrittivo.

Quale utilizzo dipende dalla natura del problema che affronti e dagli idiomi del pacchetto software con cui stai lavorando. Anche se tenderei ad evitare # 1.

    
risposta data 11.08.2014 - 21:53
fonte

Leggi altre domande sui tag