假设我有两个函数:
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
我现在想在另一个单子中一起使用它们。非常:
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是相同的类,但有不同的类型参数。另一个例子可能是堆叠a函数对:
f :: ( MonadState Int m ) => m ()
g :: ( MonadState String m ) => m ()
---------------------------------------------------- 更新 ----------------------------------------------------
根据Daniel下面的评论,我从上面添加了函数f和g的具体实例。但多亏了吉洪的回答,我想我明白了。
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)
在这种特殊情况下,不需要堆叠两个变压器——因为它们都是MonadError String
,它们可以用作相同的单子。您可以同时使用errorm
和errorms
,就像在任何其他单子中使用两个值一样。
作为一个更具体的解释,暂时忽略变压器:您可以想象这些值只是Either String Int
和Either String String
。显然,你可以一起使用它们。这就是为什么在末尾只需要一个runErrorT
而不是两个:两个值都在同一个单子中。
现在,你的实际问题是:如何堆叠两个单路变压器?它的工作原理就像组合任何两个单极变压器一样。两个状态互感器堆叠在一起,看起来就像两个不同的互感器堆叠在一起。
现在,使用它们有点棘手。根据您使用的是哪一个,您将需要以不同的方式使用lift
。如果在基本单子中有一个值,则需要提升两次。如果在内部状态单子中有一个值,则需要使用它一次。如果你在外层有一个,你就不需要它了。这就像普通的变压器。
lift (throwError "message")
。如果您确实这样做了,并且有两个堆叠的错误转换器,那么使用runErrorT
两次就可以了。