【问题标题】:How do you stack two ErrorT monad transformers on top of each other?您如何将两个 ErrorT monad 转换器堆叠在一起?
【发布时间】:2013-04-29 05:33:03
【问题描述】:

假设我有这两个功能:

errorm :: ( MonadError String m ) => Bool ->  m Int
errorm cond = if cond then return 1 else throwError "this is an error"

errorms :: ( MonadError String m ) => Bool ->  m String
errorms cond = if cond then return "works" else throwError "does not work"

如你所见,一个在安全的情况下返回一个字符串,而另一个返回一个int

我现在想在另一个 monad 中一起使用它们。琐碎:

errErr :: MonadError String m => Bool -> Bool -> m (Int, String)
errErr b1 b2 = do
    a <- errorm b1 
    b <- errorms b2
    return (a,b)

这里的函数签名是由GHC派生的,我不知道如何使用这个函数。我试过这个:

runErrorT ( runErrorT ( errErr1 True True ) )  -- should give me (Right 1, Right "works")

但它给了我:

Ambiguous type variable `e0' in the constraint:
(Error e0) arising from a use of `errErr1'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `runErrorT', namely `(errErr1 True True)'
In the first argument of `runErrorT', namely
  `(runErrorT (errErr1 True True))'
In the expression: runErrorT (runErrorT (errErr1 True True))

总的来说,这只是我的问题的一个例子。我觉得我没有掌握如何准确堆叠两个属于同一类但具有不同类型参数的 monadT。另一个例子可能是堆叠一对函数:

f :: ( MonadState Int m ) => m ()
g :: ( MonadState String m ) => m ()

----------------------------------- - - - 更新 - - - - - - - - - - - - - - - - - - - - - - --------

根据 Daniel 在下面的评论,我从上面添加了函数 f 和 g 的具体实例。但多亏了 Tikhon 的回答,我想我明白了。

type Counter = Int
type Msg     = String

incr :: (MonadState Counter m) => Counter -> m ()
incr i = modify (+i)

addMsg :: ( MonadState Msg m ) => Msg -> m()
addMsg msg = modify ( ++ msg )

incrMsg:: (MonadTrans t, MonadState Msg m, MonadState Counter (t m)) => t m ()
incrMsg = do 
    lift . addMsg $ "one"
    incr 1
    return ()

incrMsgt = runIdentity $ runStateT ( runStateT incrMsg 1 ) "hello" :: (((), Int), String)

【问题讨论】:

  • 你只需要一个runErrorTerrormerrorms 在同一个monad 中愉快地运行:runErrorT ( errErr True True ) :: [Either String (Int,String)] 产生[Right (1,"works")]
  • 在你的例子中,两者都有MonadError String m约束,只有结果类型参数不同。你不需要任何堆叠。在底部提到的一般情况下,您可以这样做,因为状态类型参数不同。你能澄清一下你到底想解决什么问题吗?
  • 感谢您的澄清,我没有很好地理解这个问题,所以我不小心将两个问题合二为一。

标签: haskell monads monad-transformers


【解决方案1】:

在这种特殊情况下,您确实不需要需要堆叠两个转换器——因为它们都是MonadError String,它们可以用作相同的 monad。您可以同时使用 errormerrorms,就像在任何其他 monad 中使用两个值一样。

作为更具体的解释,暂时忽略转换器:您可以想象这些值只是Either String IntEither String String。显然,您可以将它们一起使用。这就是为什么最后只需要一个 runErrorT 而不是两个:两个值都在同一个 monad 中。

现在,您的实际问题如何:如何堆叠两个 monad 转换器?它就像组合任何两个 monad 转换器一样工作。两个相互堆叠的状态变压器看起来就像两个不同的变压器相互堆叠。

现在,使用它们有点棘手。根据您使用的是哪一个,您需要以不同的方式使用lift。如果你在基本单子中有一个值,你需要提升两次。如果你在内部状态单子中有一个值,你将需要使用它一次。如果你在外层有一个,你就根本不需要它。这就像普通的变压器一样。

回到您的错误示例,让我们假设您实际上确实想要堆叠两个不同的错误单子转换器,而不是将它们用作一个。这意味着如果你想在 inner 中抛出一个错误,你必须写lift (throwError "message")。如果你真的这样做了并且有两个堆叠的错误转换器,那么使用runErrorT 两次就可以了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-15
    • 2012-10-16
    • 2018-11-08
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    相关资源
    最近更新 更多