Монады в контексте преобразователя монад

У меня проблемы с цеплянием за монады и преобразователи монад. У меня есть следующий надуманный пример (не компилируемый):

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

Если я скопирую функциональность readEither в readEitherT, это сработает, но у меня есть мучительное чувство, что я могу использовать мощь существующей функции readEither, но я не могу понять, как это сделать. Если я попытаюсь поднять readEither в функции readEitherT, она поднимет его до ErrorT String IO (Either String Int), как и должно быть. Но я должен как-то передать его ErrorT String IO Int.

Если я иду в неправильном направлении с этим, как правильно обрабатывать ошибки, которые требуют ввода-вывода (или других монад) и должны вызываться из монадического контекста (см. функцию foo в примере)

Редактировать: Видимо, было неясно, что я пытался сделать. Возможно, следующая функция описывает, что и почему мне было интересно

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

Это работает, но все еще уродливо из-за привязок. Это намного понятнее, чем предыдущая версия с проверкой регистра. Это рекомендуемый способ сделать это?


person Masse    schedule 16.11.2010    source источник


Ответы (1)


Непонятно, зачем вам ErrorT. Вы можете реализовать readEitherT как

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

Если вам действительно по какой-то причине нужен ErrorT, то вы можете создать служебную функцию eitherToErrorT:

eitherToErrorT = ErrorT . pure

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

[ADD] Возможно, вы просто хотите добавить ErrorT в свой стек монад...

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)

Пример:

*Main> runFoo (doIt 1 >>= doIt) (State 0 0 0)
Right 4
*Main> runFoo (doIt (-1) >>= doIt) (State 0 0 0)
Left "i < 0"
person Yuras    schedule 16.11.2010
comment
Я думал, например, сделать try(foobar) внутри ErrorT, что распространит возможную ошибку внутри монады ErrorT. (IO (Либо e a)) - person Masse; 16.11.2010
comment
Я добавил пример, как передать ошибку с помощью ErrorT, возможно, это поможет - person Yuras; 16.11.2010