【问题标题】:How to combine two different monads如何组合两个不同的单子
【发布时间】:2014-12-01 15:09:22
【问题描述】:

我正在测试一个 REST 服务器。我在 IO monad 中点击它并在State Db 中模拟它,其中Db 跟踪服务器的假定状态。以下函数应该运行两个版本并比较结果...

check :: (Eq a, MonadState d s) => s a -> IO a -> s (IO Bool)
-- or: check :: (Eq a, MonadState d s, MonadIO i) => s a -> i a -> s (i Bool)
check _ _ = (return.return) False -- for now

但是当我尝试使用这些最简单的功能时...

simReset :: State Db ()
realReset :: IO ()

reset :: StateT Db IO Bool
reset = check simReset realReset

我收到此错误:

Couldn't match expected type `Bool' with actual type `IO Bool'
Expected type: StateT Db IO Bool
  Actual type: StateT Db IO (IO Bool)
In the return type of a call of `check'
In the expression: check simReset realReset

为什么?我该如何解决?

(这个话题从这里开始:Lift to fix the *inside* of a monad transformer stack

【问题讨论】:

  • 不能s (IO Bool); simReset 给你s ~ State,但是reset 的上下文使用StateT,这是不同的。您也不能在State 上定义的函数内部使用来自StateT 的状态;将simReset 更改为通用MonadStatehoist

标签: haskell monad-transformers


【解决方案1】:

在您的实现中,check 将返回一个IO Bool,而不管状态单子s 是什么。因此,当您通过simReset 进行检查时,这是State Db monad 中的一元动作,返回值将是State Db (IO Bool)

如何解决它取决于您要执行的操作。根据您对reset 的实现,您似乎正在尝试与StateT Db IO a 形式的变压器堆栈进行交互。在这种情况下,您在StateT Db IO 的上下文中描述了一种程序。有两种方法可以解决这个问题:

  1. 您可以将simReset 升级为MonadState Db s => s () 类型(这实际上不需要任何实现更改)。

  2. 您可以定义一个辅助函数,将其“提升”到适当的 monad 中

例如:

hoistState :: Monad m => State s a -> StateT s m a
hoistState prg = StateT $ \st -> return $ runState prg st

在任何一种情况下,您都可能希望将正在执行的操作和结果保留在同一个 monad 中:

check :: (Eq a, MonadIO s, MonadState d s) => s a -> IO a -> s Bool

那么,有了解决方案一,你就有了

reset = check simReset realReset

有了解决方案二,你就有了:

reset = check (hoistState simReset) realReset      

【讨论】:

  • 疯狂似曾相识。我觉得这些都是exactly the same two approaches I suggested 在他上一个问题上的验证。
  • 我现在可以让它双向工作,但尽管你们都说同样的话,我仍然不明白原来的问题。您说我作为第一个参数传递的内容与返回值匹配。为什么?我从来没有告诉过它。我什至从来没有叫过它。我想我所做的是将a 限制为(),第二个参数将s 限制为State Db,并将返回值限制为State Db (IO Bool),就像我想要的那样。我只是看不出是什么迫使s 成为State Db IO
  • @AdrianMay 我不知道如何解释你的想法出了什么问题,原因有二:1. 你没有注意StateStateT 之间的区别。我不确定这只是输入错误还是真正的混淆来源。 2. 你说你想要State Db (IO Bool),但是你写的代码有一个类型签名说你想要StateT Db IO Bool。同样,我不确定这是否只是错误输入还是真正的混淆来源。如果你能把这些事情弄清楚,我们也许可以给出更多的解释,但我不确定。
  • 我以为State X 只是StateT X Identity。我不认为我对 StateT X 想要一个 monad 进行操作(它本身需要一个类型)而 State X 只需要那个类型这一事实感到困惑。至于我的函数的返回类型,我真的不在乎,只要它能让我表达两个结果的相等或不相等,其中一个来自某种有状态计算,另一个来自 HTTP GET .
  • @AdrianMay check :: ... -> s (IO Bool) 声称check 返回一个产生IO 动作的单子动作(产生Bool)。另一方面,reset :: StateT Db IO Bool 声称reset 返回一个直接产生Bool 的单子动作。这两个声明不兼容,因为reset 是通过调用check 实现的——你的一元动作不能同时产生BoolIO 动作。
猜你喜欢
  • 1970-01-01
  • 2016-07-20
  • 1970-01-01
  • 2019-12-02
  • 2015-06-12
  • 2012-09-02
  • 2019-01-02
  • 2023-03-29
  • 2017-07-21
相关资源
最近更新 更多