Monad变换器上下文中的Monad

4

我对单子和单子变换器感到困惑。以下是一个人为构造的例子(无法编译):

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函数的能力,但我无法弄清楚如何做到这一点。如果我尝试在readEitherT函数中提升readEither函数,它应该将其提升为ErrorT String IO (Either String Int),但我应该以某种方式将其变为ErrorT String IO Int

如果我走错方向了,那么处理需要IO(或其他单子)并要从单子上下文中调用的错误的正确方法是什么(请参见示例中的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

这个代码段可行,但是因为绑定变量而显得糟糕。它比之前有case检查的版本清晰得多。这是推荐的做法吗?
1个回答

2

不清楚您为什么需要使用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"

我在考虑在ErrorT中执行try(foobar),这将在ErrorT单子内传播可能的错误。(IO (Either e a)) - Masse
我已经添加了一个使用ErrorT传播错误的示例,也许会有所帮助。 - Yuras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接