我对单子和单子变换器感到困惑。以下是一个人为构造的例子(无法编译):
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检查的版本清晰得多。这是推荐的做法吗?
ErrorT
传播错误的示例,也许会有所帮助。 - Yuras