2016-05-31 31 views
11

In qualche modo mistificato dal seguente codice. Nella versione non-giocattolo del problema sto provando a fare un calcolo monadico in un risultato monad, i cui valori possono essere costruiti solo dall'IO. Sembra che la magia dietro IO renda tali calcoli rigorosi, ma non riesco a capire come esattamente ciò accada.La monade IO impedisce il cortocircuito della mappaM incorporata?

Il codice:

data Result a = Result a | Failure deriving (Show) 

instance Functor Result where 
    fmap f (Result a) = Result (f a) 
    fmap f Failure = Failure 

instance Applicative Result where 
    pure = return 
    (<*>) = ap 

instance Monad Result where 
    return = Result 
    Result a >>= f = f a 
    Failure >>= _ = Failure 

compute :: Int -> Result Int 
compute 3 = Failure 
compute x = traceShow x $ Result x 

compute2 :: Monad m => Int -> m (Result Int) 
compute2 3 = return Failure 
compute2 x = traceShow x $ return $ Result x 

compute3 :: Monad m => Int -> m (Result Int) 
compute3 = return . compute 

main :: IO() 
main = do 
    let results = mapM compute [1..5] 
    print $ results 
    results2 <- mapM compute2 [1..5] 
    print $ sequence results2 
    results3 <- mapM compute3 [1..5] 
    print $ sequence results3 
    let results2' = runIdentity $ mapM compute2 [1..5] 
    print $ sequence results2' 

L'output:

1 
2 
Failure 
1 
2 
4 
5 
Failure 
1 
2 
Failure 
1 
2 
Failure 

risposta

10

casi di test Nizza. Ecco cosa sta succedendo:

  • in mapM compute vediamo la pigrizia al lavoro, come al solito. Nessuna sorpresa qui.

  • in mapM compute2 lavoriamo all'interno monade IO, la cui definizione mapM richiederà l'intero elenco: a differenza Result che salta la coda della lista non appena Failure viene trovato, IO sarà sempre scandire l'intero elenco. Nota il codice:

    compute2 x = traceShow x $ return $ Result x 
    

    Quindi, quanto sopra wil stampare il messaggio di debug non appena ciascun elemento della lista di azioni IO si accede. Tutti lo sono, quindi stampiamo tutto.

  • in mapM compute3 ora usiamo, più o meno:

    compute3 x = return $ traceShow x $ Result x 
    

    Ora, dal momento che in return IO è pigro, lo farà non grilletto traceShow al ritorno l'azione IO. Quindi, quando viene eseguito mapM compute3, viene visualizzato nessun messaggio. Invece, vediamo i messaggi solo quando viene eseguito sequence results3, che forza lo Result - non tutti, ma solo quanto necessario.

  • l'esempio finale Identity è anche abbastanza difficile. Nota questo:

    > newtype Id1 a = Id1 a 
    > data Id2 a = Id2 a 
    > Id1 (trace "hey!" True) `seq` 42 
    hey! 
    42 
    > Id2 (trace "hey!" True) `seq` 42 
    42 
    

    quando si utilizza un newtype, in fase di esecuzione non c'è boxing/unboxing (sollevamento AKA) coinvolti, quindi forzando un valore Id1 x provoca x essere costretti. Con i tipi data ciò non accade: il valore è racchiuso in una scatola (ad esempio Id2 undefined non equivale a undefined).

    Nell'esempio, si aggiunge un costruttore Identity, ma quello è dallo newtype Identity !! Così, quando si chiama

    return $ traceShow x $ Result x 
    

    il return qui non avvolgere nulla, e il traceShow è immediatamente attivato non appena mapM viene eseguito.

+0

Grazie mille per la risposta, chi. Posso chiederti come fai a sapere che la definizione IO di mapM è severa e che il ritorno è pigro? – NioBium

+3

@NioBium 'Fallimento >> = f = Fallimento' scarta il' f': non è necessario procedere ulteriormente sulla catena monadica. IO ha una definizione di leva inferiore che non è facilmente scrivibile, ma - salvo eccezioni - 'action >> = f' chiamerà sempre' f' poiché ci si aspetta che ad es. 'action >> print 4' finirà per stampare 4 indipendentemente da cosa fa l'azione (escludendo eccezioni IO e non-terminazione). – chi

+0

Giusto. Grazie ancora! – NioBium

1

tuo tipo Result sembra essere praticamente identico a Maybe, con

Result <-> Just 
Failure <-> Nothing 

Per amore del mio povero cervello, mi atterrò a Maybe terminologia nel resto di questa risposta.

chi ha spiegato perché IO (Maybe a) non cortocircuita nel modo previsto. Ma c'è è un tipo che puoi usare per questo genere di cose! È essenzialmente lo stesso tipo, infatti, ma con un'istanza diversa Monad. Puoi trovarlo in Control.Monad.Trans.Maybe. Sembra qualcosa di simile:

newtype MaybeT m a = MaybeT 
    { runMaybeT :: m (Maybe a) } 

Come potete vedere, questo è solo un wrapper per newtypem (Maybe a). Ma il suo esempio Monad è molto diversa:

instance Monad m => Monad (MaybeT m) where 
    return a = MaybeT $ return (Just a) 
    m >>= f = MaybeT $ do 
    mres <- runMaybeT m 
    case mres of 
     Nothing -> return Nothing 
     Just a -> runMaybeT (f a) 

Cioè, m >>= f corre il m calcolo nella monade sottostante, ottenendo Maybe qualcosa o altro. Se ottiene Nothing, si ferma semplicemente, restituendo Nothing. Se ottiene qualcosa, lo passa a f e esegue il risultato. È inoltre possibile trasformare qualsiasi m azione in un "successo" MaybeT m dell'azione utilizzando lift da Control.Monad.Trans.Class:

class MonadTrans t where 
    lift :: Monad m => m a -> t m a 

instance MonadTrans MaybeT where 
    lift m = MaybeT $ Just <$> m 

È inoltre possibile utilizzare questa classe, definita da qualche parte come Control.Monad.IO.Class, che spesso è più chiara e può essere molto più conveniente:

class MonadIO m where 
    liftIO :: IO a -> m a 

instance MonadIO IO where 
    liftIO m = m 

instance MonadIO m => MonadIO (MaybeT m) where 
    liftIO m = lift (liftIO m) 
Problemi correlati