Монады в контексте монадного трансформатора
У меня проблемы с захватом монад и трансформаторов монад. У меня есть следующий надуманный пример (не компилируется):
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 в readEterT, это работает, но у меня возникает неприятное ощущение, что я могу использовать всю мощь существующей функции 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
Это работает, но все еще безобразно из-за привязок. Это намного понятнее, чем в предыдущей версии, в которой была проверка регистра. Это рекомендуемый способ сделать это?
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
[ДОБАВИТЬ] Может быть, вы просто хотите добавить 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"