2011-12-16 5 views
15

Dato il seguente:Estrazione di una Forse valore nel IO

> (liftM2 fromMaybe) (ioError $ userError "OOPS") (return $ Just "ok") 

ghci mi dà

*** Exception: user error (OOPS) 

Naturalmente, fromMaybe funziona correttamente:

> (liftM2 fromMaybe) (return $ "not me") (return $ Just "ok") 
"ok" 

Ma sembra che il L'operazione IO viene eseguita e quindi scartata:

> (liftM2 fromMaybe) (putStrLn "computing.." >> "discarded") (return $ Just "ok") 
computing.. 
"ok" 

Perché sta succedendo? C'è un modo per rendere l'IO più pigro?

In particolare, dato value :: IO (Maybe a) che cosa è un (condensato pulito,) modo di dire

result <- (liftM2 fromMaybe) err value 

e lo hanno scompattare provocare o lanciare un IOError di conseguenza?

risposta

12

Non so che fare il lazier IO è la direzione giusta qui. Quello che sembri voler fare è arrivare allo Maybe, quindi eliminarlo. Questo può essere scritto in diversi modi, ecco una possibilità:

test :: IO (Maybe a) -> IO a 
test = (>>= maybe (ioError $ userError "oops") return) 
10

Se traducete da liftM2 fare notazione, è ovvio il motivo per cui il codice non riesce:

do x <- ioError $ userError "OOPS" 
    y <- return $ Just "ok" 
    return $ fromMaybe x y 

Questo non potrà mai andare oltre la prima linea , poiché lancia incondizionatamente un'eccezione.

Anthony's suggestion funzionerà bene, ma se non vi interessa circa la specifica eccezione gettato, è anche possibile utilizzare il pattern matching:

do Just result <- value 

Se il pattern non corrisponde, questa chiamerà fail, che nel caso della monade IO si genera un'eccezione.

> Just x <- return Nothing 
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:0-5) 
5

che cosa è un modo (condensato pulito,) per ... scompattare [la] risultato o gettare un IOError di conseguenza?

Si consiglia di evitare di fare affidamento sugli errori di lancio. Invece, gestisci l'errore in modo esplicito:

maybeM :: Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b 
maybeM err f value = do 
    x <- value 
    case x of 
    Just y -> f y 
    Nothing -> err 

-- This can be written simply as: 
maybeM err f value = do 
    x <- value 
    maybe err f x 

-- or even shorter! This is starting to look like Anthony's answer :) 
maybeM err f value = value >>= maybe err f 

Gli input ei tipi della funzione devono parlare da soli. Lo usi dandogli un'azione da eseguire per il caso Nothing o una funzione da eseguire sul valore interno per il caso Just.Per gli ingressi particolari questo sarà simile:

maybeM (ioError $ userError "OOPS") return (return $ Just "ok") 

Quindi, se si assolutamente necessario, poi il "modo conciso per decomprimere il risultato o lanciare un IOError" sarebbe:

-- compare to fromJust, a function to be avoided 
fromJustIO :: IO (Maybe a) -> IO a 
fromJustIO = maybeM (ioError $ userError "OOPS") return 

Notate come la firma del tipo per questo è praticamente Maybe a -> a, che è l'essenza di magicMonadUnwrap :: Monad m => m a -> a, che dovrebbe attivare alcune bandiere rosse. Tuttavia, è possibile utilizzare questa atrocità in modo semplice:

result <- fromJustIO value 

Anche se ancora una volta, io sconsiglio fortemente l'uso delle eccezioni qui. Prova a gestire gli errori in modo più elegante che semplicemente esplodendo, utilizzando maybeM e fornendo un'azione IO da eseguire in caso di errore.

+0

Grazie per la preoccupazione, ma è un po 'infondata. Sto gettando gli errori in una serie di funzioni di secondo livello che ognuno fa il suo piccolo bit di I/O per prendere qualche configurazione. La funzione di configurazione di primo livello li prova tutti con un "catch" e formatta bene ogni errore. Mi consente di mantenere la gestione degli errori in un'unica posizione senza dover eseguire il threading di una funzione "handleErr" attraverso tutte le funzioni di secondo livello. In uno scenario del genere, gli errori sono ancora una cattiva idea? – So8res

+4

So8res: personalmente avrei le funzioni di secondo livello che producono i valori di 'Maybe', e quindi la funzione di configurazione di primo livello può raggrupparli insieme con qualcosa come' sequence' o '<$>' e '', o gestire ciascuno errore potenziale separatamente, come desiderato. È una questione di gusti, ma a mio parere, gli errori sono quasi sempre una cattiva idea in Haskell, poiché abbiamo potenti astrazioni che ci permettono di comporre 'Maybe'. Il meccanismo di controllo del flusso di tiro/presa non corrisponde allo stile FP che Haskell incoraggia. –