【问题标题】:Use list monad inside monad transformer type classes?在单子变压器类型类中使用列表单子?
【发布时间】:2014-08-03 10:34:15
【问题描述】:

我的目标是创建一个在 ReaderT WriterT 堆栈或 RWS 堆栈中使用 list monad 的函数。更一般地,如何在 MonadReader、MonadWriter 等 mtl 类型类中使用 list monad?

我为什么要这样做?这个问题是Beginning Haskell 中的一个练习。它要求我“使用 MonadReader 和 MonadWriter 包装基本列表 monad 的功能。要检查该功能是否通用,请使用两个不同的 monad 来 [测试] 请求的功能:ReaderT r (WriterT w []) aRWST r w s m a”所以这本书暗示这是可能的。

我不知道如何“告诉”编译器使用 list monad。如果我使用 ask >>= liftask >>= lift . lift 我可以让 2 级堆栈 (RWST []) 或 3 级堆栈 (ReaderT WriterT []) 工作,但不能同时工作。

我问题的重点:

pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

另外,我想知道如何键入函数。到目前为止,我最好的尝试看起来像 pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) =&gt; Int -&gt; Int -&gt; m () 我知道这是不对的,可能缺少列表单子。另外,我认为 MonadPlus 在类型签名中可能有用,但我不太确定。

这一行:do (s0, e0) &lt;- ask &gt;&gt;= lift 给我带来了麻烦。我尝试了 0、1 和 2 次升降机,但均未成功。我想ask 获取[(Int, Int)],然后使用列表单子来处理(Int, Int)(并让列表单子为我尝试所有可能性)。

作为练习的一部分,我需要能够使用这两个函数(或非常相似的函数)调用pathImplicitStack'

pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
  where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()

pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
  where exec = execRWST (pathImplicitStack' start end) edges ()

这和我之前的问题有关:How do I use list monad inside of ReaderT?

整个文件方便测试:

{-# LANGUAGE FlexibleContexts #-}

module Foo where

import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.RWS

graph1 :: [(Int, Int)]
graph1 = [(2013,501),(2013,1004),(501,2558),(1004,2558)]


pathImplicitRW :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRW edges start end = execWriterT rdr
  where rdr = runReaderT (pathImplicitStack' start end) edges :: WriterT [Int] [] ()

pathImplicitRWS :: [(Int, Int)] -> Int -> Int -> [[Int]]
pathImplicitRWS edges start end = map snd exec
  where exec = execRWST (pathImplicitStack' start end) edges ()

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

编辑

根据我尝试过的 John L 的反馈

pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t []), MonadTrans t) => Int -> Int -> t [] ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      guard $ s0 == start
      tell [start]
      pathImplicitStack' e0 end

但正如他所指出的,它只能与一个 monad 转换器一起使用来包装列表 monad,即 RSWT,并且不能与 ReaderT WriterT 一起使用。所以这不是我正在寻找的解决方案。

【问题讨论】:

  • MonadPlus 基本上是“list monad”类型的类。例如:cons a as = return a `mplus` asnil = mzero

标签: haskell monad-transformers monadplus


【解决方案1】:

我认为这里的困难在于你混合了 monad 的层。看着

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> [m ()]

此函数返回m () 计算列表,但是ask &gt;&gt;= lift(和您之前的问题)假定List 是您在其上堆叠额外转换器的基本单子。如果你想使用 List 作为基础 monad,你需要改变 pathImplicitStack' 的类型

pathImplicitStack' :: (MonadReader [(Int, Int)] (t []), MonadWriter [Int] (t []), MonadPlus (t [])) => Int -> Int -> t [] ()

但即使这样也不够通用,因为这只允许在列表顶部添加单个转换器。您可以使用类型运算符库将两个 monad 转换器组合成一个转换器,但这似乎有点复杂。

这是一种选择:使用 Identity 作为基础 monad,并在该 monad 堆栈之外执行列表操作。 (警告,所有代码未经测试,甚至可能无法编译)

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()
pathImplicitStack' start end | start == end = tell [end]
pathImplicitStack' start end =
  do  (s0, e0) <- ask >>= lift
      edges <- filter ((== start) . fst) <$> ask
      let m (s0,e0) = tell [s0] >> pathImplicitStack' e0 end
      mapM_ m edges

还有另一种选择。您可以使用ListTLogicT 作为外部转换器,让您使用此功能(或类似功能):

pathImplicitStack'2 :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m) => Int -> Int -> ListT m ()
pathImplicitStack'2 start end | start == end = tell [end]
pathImplicitStack'2 start end =
  do  (s0, e0) <- ask
      guard $ s0 == start
      tell [start]
      pathImplicitStack'2 e0 end
-- if you have MonadReader/MonadWriter instances for ListT in scope, I think this will
-- work.  But if they aren't available, you will need to use `lift ask` and 
-- `lift (tell ...)`

我几乎肯定会选择第一种方法。

【讨论】:

  • 遗憾的是,本书中呈现给我的练习(请参阅我的编辑)希望我的函数能够被 ReaderT WriterT 堆栈和 RSWT 使用,并且它希望它们能够“包装基本列表单子'。因此,出于练习的目的,第一种方法不够通用。这本书没有介绍任何类型运算符库,所以我很确定这不是“答案”。如果 list 不是内部 monad,我将无法从 Writer 中得到正确的答案 [[Int]]
  • @DanielK 您可以在函数中简单地将lift 替换为liftBase(来自hackage.haskell.org/package/transformers-base),这样就可以了。但我也不认为liftBase 是预期答案的一部分。
【解决方案2】:

因此,在问题要求范围内执行此操作的基本问题是,没有 MTL 库函数可以从可能是任意级别的列表 monad 中提升。但是,您可以“欺骗”一点:组合 MonadMonadPlus 实例 继承自底层列表 monad,无论深度如何,您都可以使用它来生成所需的操作:

  do  (s0, e0) <- ask >>= msum . map return

然后类型签名也有错误,需要改成:

pathImplicitStack' :: (MonadReader [(Int, Int)] m, MonadWriter [Int] m, MonadPlus m) => Int -> Int -> m ()

编辑:实际上再三考虑这实际上并不是作弊。它只是使用MonadPlus API 来链接替代操作,而不是直接使用底层列表 monad。

【讨论】:

  • 成功了,谢谢!我也不认为 MonadPlus 作弊。我已经在书中接触过它,所以对我来说似乎是公平的游戏。
猜你喜欢
  • 1970-01-01
  • 2018-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-27
  • 2012-02-21
  • 1970-01-01
相关资源
最近更新 更多