2011-11-17 8 views
30

Sono abbastanza nuovo per Haskell e ho cominciato lentamente a pensare che ci sia qualcosa di sbagliato nell'esistenza di Monad. Real World Haskell warns against its use ("Ancora una volta, ti consigliamo di evitare quasi sempre l'errore!"). Ho appena notato oggi che Ross Paterson l'ha definita "una verruca, non un motivo di progettazione" back in 2008 (e sembrava avere un po 'di accordo in quel thread).Devo evitare di usare Monad fallire?

Mentre guardavo il dottor Ralf Lämmel talk on the essence of functional programming, ho iniziato a capire una possibile tensione che potrebbe aver portato al fallimento di Monad. Nella conferenza, Ralf parla dell'aggiunta di vari effetti monadici a un parser monadico di base (registrazione, stato, ecc.). Molti degli effetti richiedevano modifiche al parser di base e talvolta ai tipi di dati utilizzati. Ho pensato che l'aggiunta di "fail" a tutte le monadi potrebbe essere stato un compromesso perché "fail" è così comune e si desidera evitare le modifiche al parser "base" (o qualsiasi altra cosa) il più possibile. Ovviamente, una sorta di "fail" ha senso per i parser ma non sempre, per esempio, mette/ottieni di stato o chiedi/local di Reader.

Fammi sapere se potrei essere sulla strada sbagliata.

Devo evitare di usare Monad fallire? Quali sono le alternative a Monad fallite? Ci sono delle librerie monad alternative che non includono questa "verruca design"? Dove posso leggere di più sulla storia di questa decisione progettuale?

+4

[Real World Haskell dice] (http://book.realworldhaskell.org/read/monads.html#monads.monad.fail) "[in molte monadi]' fail' usa 'error'. Chiamando' error' di solito è altamente indesiderabile, poiché genera un'eccezione che i chiamanti non possono prendere o non si aspetteranno. " – Miikka

+3

Risposta breve: sì. –

+2

Credo che "fail" sia necessario per gestire i fallimenti della corrispondenza dei pattern quando ci si comporta così: "Just x <- somethingReturningIoMaybe". –

risposta

27

Alcune monadi hanno un meccanismo di guasto sensibile, ad es. monade terminale:

data Fail x = Fail 

Alcuni monadi non hanno un meccanismo di rottura sensibile (undefined non è sensibile), ad esempio la monade iniziale:

data Return x = Return x 

In questo senso, è chiaramente una verruca a richiedere tutte le monadi avere un metodo fail. Se stai scrivendo programmi astratti su monadi (Monad m) =>, non è molto salutare utilizzare il metodo fail generico di m. Ciò risulterebbe in una funzione che puoi istanziare con una monade in cui fail non dovrebbe esistere realmente.

vedo un minor numero di obiezioni utilizzando fail (soprattutto indirettamente, facendo corrispondere Pat <- computation) quando si lavora in una monade specifico per il quale una buona fail comportamento è stato chiaramente specificato. Si spera che tali programmi sopravvivranno a un ritorno alla vecchia disciplina in cui la corrispondenza di modelli non banali ha creato una domanda di MonadZero invece di solo Monad.

Si potrebbe obiettare che la migliore disciplina è sempre quella di trattare i casi di fallimento in modo esplicito. Mi oppongo a questa posizione su due fronti: (1) che il punto della programmazione monadica è quello di evitare tale confusione, e (2) che la notazione corrente per l'analisi del caso sul risultato di una computazione monadica sia così terribile. La prossima versione di SHE supporterà la notazione (trovato anche in altre varianti)

case <- computation of 
    Pat_1 -> computation_1 
    ... 
    Pat_n -> computation_n 

che potrebbe aiutare un po '.

Ma questa intera situazione è un casino spiacente. Spesso è utile caratterizzare le monadi per le operazioni che supportano. È possibile vedere fail, throw, ecc. Come operazioni supportate da alcune monadi ma non da altre. Haskell rende piuttosto maldestro e costoso supportare piccoli cambiamenti localizzati nel set di operazioni disponibili, introducendo nuove operazioni spiegando come gestirle in termini di quelle vecchie. Se vogliamo seriamente fare un lavoro più ordinato qui, abbiamo bisogno di ripensare come funziona catch, per renderlo un traduttore tra diversi meccanismi di gestione degli errori locali.Spesso desidero eseguire il bracketing di un calcolo che può fallire in modo ininfluente (ad esempio, per errore di corrispondenza del modello) con un gestore che aggiunge più informazioni contestuali prima di trasmettere l'errore. Non posso fare a meno di pensare che a volte è più difficile farlo che dovrebbe essere.

Quindi, questo è un problema potrebbe fare meglio, ma per lo meno, utilizzare fail solo per monadi specifiche che offrono un'implementazione ragionevole e gestire correttamente le 'eccezioni'.

+0

Scusami, che cos'è "SHE" che hai menzionato? –

+1

È lo Strathclyde Haskell Enhancement, il mio preprocessore che supporta le funzionalità sperimentali (principalmente i tipi dipendenti falsi). Ma in questi giorni, SHE è stato reso superfluo dall'adozione di gran parte di tale funzionalità in GHC. – pigworker

24

In Haskell 1.4 (1997) non esisteva il numero fail. Invece, c'era una classe di tipo MonadZero che conteneva un metodo zero. Ora, la notazione do utilizzata zero per indicare un errore di corrispondenza del modello; ciò ha causato sorprese alle persone: se la loro funzione fosse necessaria Monad o MonadZero dipendeva dal modo in cui usavano la notazione do in essa.

Quando Haskell 98 è stato progettato un po 'più tardi, hanno apportato diverse modifiche per rendere la programmazione più semplice per i principianti. Ad esempio, le comprensioni di monade sono state trasformate in comprensione delle liste. Allo stesso modo, per rimuovere il problema della classe di tipo do, la classe MonadZero è stata rimossa; per l'uso di do, il metodo fail è stato aggiunto a Monad; e per altri usi di zero, è stato aggiunto un metodo mzero a MonadPlus.

C'è, penso, una buona argomentazione da fare che fail non dovrebbe essere usato per qualcosa esplicitamente; la sua sola destinazione d'uso è nella traduzione della notazione do. Tuttavia, io stesso sono spesso cattivo e uso anche fail esplicitamente.

È possibile accedere ai report originali 1.4 e 98 here. Sono sicuro che la discussione che porta al cambiamento può essere trovata in alcuni archivi di liste di email, ma non ho collegamenti utili.

+0

" Io stesso sono spesso cattivo e uso 'fail' esplicitamente ". Spesso? Spiegare. –

+6

@DanBurton: cosa c'è da spiegare? Se sono in un contesto monadico, tendo ad usare 'fail' per segnalare un errore, a meno che non abbia una specifica necessità di usare un altro meccanismo. – ibid

+0

Grazie. È bello sapere della storia. Non sapevo che in origine Haskell aveva solo una notazione (senza liste di comprensione). Ho trovato un thread interessante nell'archivio della mailing list http://www.mail-archive.com/[email protected]/msg03002.html –

6

Cerco di evitare il fallimento di Monad laddove possibile, e ci sono una varietà di modi per catturare il fallimento a seconda delle circostanze. Edward Yang ha scritto una buona panoramica sul suo blog nell'articolo 8 ways to report errors in Haskell revisited.

In sintesi, i diversi modi di segnalare gli errori che si identifica sono:

  1. Usa errore
  2. Usa Forse un
  3. utilizzare sia String un
  4. Usa Monade e non riescono a generalizzare 1- 3
  5. Usa MonadError e un tipo di errore personalizzata
  6. Usa gettare la monade IO
  7. Usa ioError e cattura
  8. dadi Go con trasformatori monade
  9. eccezioni controllate
  10. mancata

Di questi, sarei tentato di utilizzare l'opzione 3, con Either e b se so come gestire l'errore , ma ha bisogno di un po 'più di contesto.

+1

Oppure 'Maybe' ... L'utilizzo di uno dei due è bello. È fastidioso usare una libreria che usa Either e una libreria che utilizza Maybe nello stesso codice, però. I trasformatori Monad non sono così belli ... – alternative