mtl,reader,异常和堆叠顺序

mtl, reader, exceptt & stacking order

所以这会有点长,因为我不确定如何更笼统地提出这个问题。好消息是我在问题的底部有一个代码示例,这个想法只是让它构建和优雅:-)

我有几个函数的签名如下:

calledFunction
  :: (MonadReader env m, HasToken env, MonadIO m)
  => m (Either MyError MyResult)

calledFunction2
  :: (MonadReader env m, HasToken env, MonadIO m)
  => m (Either MyError MyResult2)

最后我想得到类型 ExceptT String IO MyResult3 的结果,我通过组合 MyResult & MyResult2.

得到

现在 calledFunction returns 和 Either 非常好,因为我可以利用:

ExceptT :: m (Either e a) -> ExceptT e m a

我只是输入 EitherT calledFunction,我不会再输入 m (Either MyError MyResult),而是直接输入 ExceptT MyError m MyResult)。进步了!

但我还需要为 calledFunction 提供它想要的 reader 上下文。现在,我会用 runReaderT 来做到这一点。我现在已经来到 ExceptT MyError m MyResult 变压器堆栈,所以 ReaderT 自然应该去 m 所在的位置。所以 ExceptT MyError (ReaderT Config IO) MyResult...

除了,我如何 'fill in' 具有要读取的值的 readerT,因为它位于转换器堆栈的底部?如果我反转堆栈使 reader 位于顶层,那么 runReaderT 自然会出现,但我不知道如何使用 EitherT 将我的 Either 转换为ExceptT 优雅...

import Control.Monad.Reader
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Except
import Control.Monad.IO.Class
import Control.Error -- 'error' package

class HasToken a where
    getToken :: a -> String
data Config = Config String
instance HasToken Config where
    getToken (Config x) = x

data MyError = MyError String deriving Show
data MyResult = MyResult String
data MyResult2 = MyResult2 String

data MyResult3 = MyResult3 MyResult MyResult2

calledFunction
  :: (MonadReader env m, HasToken env, MonadIO m)
  => m (Either MyError MyResult)
calledFunction = undefined

calledFunction2
  :: (MonadReader env m, HasToken env, MonadIO m)
  => m (Either MyError MyResult2)
calledFunction2 = undefined

cfg = Config "test"

main = undefined

test :: ExceptT MyError IO MyResult3
test = do
    -- calling runReaderT each time defeats the purpose..
    r1 <- ExceptT (runReaderT calledFunction cfg)
    r2 <- ExceptT (runReaderT calledFunction2 cfg)
    return $ MyResult3 r1 r2

test1 = runReaderT test2 cfg

test2 :: ReaderT Config (ExceptT MyError IO) MyResult3
test2 = do
    -- how to make this compile?
    let cfg = Config "test"
    r1 <- ExceptT calledFunction
    r2 <- ExceptT calledFunction2
    return $ MyResult3 r1 r2

您可以使用 hoist from Control.Monad.Morph 到 运行 ExceptT 下面的 Reader:

ghci> let foo = undefined :: ExceptT () (ReaderT () IO) ()
ghci> :t hoist (flip runReaderT ()) foo
hoist (flip runReaderT ()) foo :: ExceptT () IO ()

自己做起来也很容易,你只需要用 runExceptT 解包,用 runReader 提供环境,然后在 ExceptT 构造函数中重新包装结果:

ghci> :t \env -> ExceptT . flip runReaderT env . runExceptT
\env -> ExceptT . flip runReaderT env . runExceptT
    :: r -> ExceptT e (ReaderT r m) a -> ExceptT e m a