我无法处理以下问题:
我有一些功能,返回foo :: a -> b -> ErrorT String IO Int
我知道它会返回IO (Either String Int)
尽管如此,我还有更复杂的功能,它返回:
bar :: a -> b -> StateT Char (ReaderT Char (ErrorT String IO)) Int
此函数bar
调用函数foo
。我要跟随的事情:
如果foo
调用throwError
bar
也会引发相同的错误。如果foo
返回Int
bar
,则返回Int
。
但是,由于不匹配类型,这是可能的。
我不知道如何以优雅的方式做到这一点。 我认为我的transformator monad订单不行。
答案 0 :(得分:2)
这样的事情应该有效:
bar x y = do
-- some code
z <- lift $ lift $ foo x y
-- some more code
return z
答案 1 :(得分:2)
我通常使用的是我所称的 Transformer Monad Classes :
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.Reader
import Control.Monad.State
inner :: ReaderT Char IO Int
inner = do
a <- ask
lift $ print a
return 5
inner' :: (MonadReader Char m, MonadIO m) => m Int
inner' = do
a <- ask
liftIO $ print a
return 5
outer :: StateT Char (ReaderT Char IO) Int
outer = do
a <- get
b <- lift $ inner -- need to lift
c <- inner' -- no need to lift
lift . lift $ print "need to lift twice to get to IO"
return 5
main = runReaderT (runStateT outer 'b') 'a'
让我们把它分解;第一个inner
函数具有您需要直接抬起以便使用它的具体类型。但是,如果您参数化它只在签名中留下 capabilities ,则可以跳过提升,只要它在堆栈中明确地获取此功能。在这种情况下,它很明确,因为StateT Char (ReaderT Char IO) Int
只有MonadIO
的一个实例(来自IO
)和MonadReader Char
的一个实例(来自ReaderT ... Char
})。
现在,只要实例清晰,您需要制作多少部电梯并不重要!考虑:
outer' :: ErrorT String (StateT Char (ReaderT Char IO)) Int
outer' = do
a <- inner' -- still no need to lift!
b <- lift . lift $ inner -- need to double lift in this case
return 5
最后可能不明显的是outer
的签名也可以用通用的方式表达:
outer :: (MonadState Char m, MonadReader Char m, MonadIO m) => m Int
它仍然可以在没有任何提升的情况下工作(对于IO操作没有liftIO
,因为像print
这样的函数是根据IO
定义的,而不是MonadIO m
。比较,像ask
和get
这样的函数是根据相应的MonadX
类来定义的,这样我们就可以跳过提升了。