2010-11-16 35 views
4

J'ai de la difficulté à saisir les monars et les transformateurs monad. Je le exemple suivant artificiel (non compilable):Monades dans un contexte de transformateur monad

import Control.Monad 
import Control.Monad.Error 
import Control.Monad.Reader 

data State = State Int Int Int 
type Foo = ReaderT State IO 

readEither :: String -> Either String Int 
readEither s = let p = reads s 
      in case p of 
       [] -> throwError "Could not parse" 
       [(a, _)] -> return a 

readEitherT :: IO (Either String Int) 
readEitherT = let p s = reads s 
      in runErrorT $ do 
    l <- liftIO (getLine) 
    readEither l 

foo :: Foo Int 
foo = do 
    d <- liftIO $ readEitherT 
    case d of 
     Right dd -> return dd 
     Left em -> do 
    liftIO $ putStrLn em 
    return (-1) 

bar :: Foo String 
bar = do 
    liftIO $ getLine 

defaultS = State 0 0 0 

Si je copie la fonctionnalité de readEither à readEitherT, cela fonctionne, mais je un sentiment lancinant que je peux tirer parti de la puissance du readEither existant fonction, mais je ne peux pas comprendre comment. Si j'essaie de lever le readEither dans la fonction readEitherT, il l'élève à ErrorT String IO (Either String Int) comme il se doit. Mais je devrais en quelque sorte l'obtenir à ErrorT String IO Int.

Si je vais dans la mauvaise direction avec cela, quelle est la bonne façon de erreurs de poignée qui nécessitent IO (ou d'autres monades) et doivent être appelés à partir contexte monadique (voir la fonction foo dans l'exemple

Editer: Apparemment, ce que j'essayais de faire n'était pas clair. Peut-être la fonction suivante décrit ce que et pourquoi je me demandais

maybePulseQuit :: Handle -> IO (Either String()) 
maybePulseQuit h = runErrorT $ do 
    f <- liftIO $ (communicate h "finished" :: IO (Either String Bool)) 
    (ErrorT . pure) f >>= \b → liftIO $ when b $ liftIO pulseQuit 

Cela fonctionne, mais est encore moche à cause des liaisons. C'est beaucoup plus clair que la version précédente qui avait un contrôle de cas. Est-ce la façon recommandée de le faire?

Répondre

2

Il n'est pas clair pourquoi vous avez besoin de ErrorT. Vous pouvez mettre en œuvre readEitherT comme

readEitherT :: IO (Either String Int) 
readEitherT = fmap readEither getLine 

Si vous avez vraiment besoin ErrorT pour une raison quelconque, vous pouvez créer une fonction d'utilité eitherToErrorT:

eitherToErrorT = ErrorT . pure 

readEitherT = runErrorT $ do 
    l <- liftIO $ getLine 
    eitherToErrorT $ readEither l 

[ADD] Peut-être vous voulez juste ajouter ErrorT dans votre pile de monade ...

data State = State Int Int Int 
type Foo = ErrorT String (ReaderT State IO) 

runFoo :: Foo a -> State -> IO (Either String a) 
runFoo foo s = runReaderT (runErrorT foo) s 

doIt :: Int -> Foo Int 
doIt i = if i < 0 
      then throwError "i < 0" 
      else return (i * 2) 

Exemple:

*Main> runFoo (doIt 1 >>= doIt) (State 0 0 0) 
Right 4 
*Main> runFoo (doIt (-1) >>= doIt) (State 0 0 0) 
Left "i < 0" 
+0

Je pensais par exemple faire try (foobar) dans ErrorT qui propagerait l'erreur possible dans la monade ErrorT. (IO (Either e a)) – Masse

+0

J'ai ajouté un exemple de propagation d'erreur en utilisant 'ErrorT', peut-être que cela aidera – Yuras