WriterT转换列表monad - 内部和外部monad如何协同工作?

时间:2016-11-18 06:15:32

标签: haskell

在第181页的 Beginning Haskell 一书中,有一个使用WriterT包装List monad的示例。下面的代码计算图表中的路径。请注意,这是一个非常简单的算法,不考虑循环)。

type Vertex = Int
type Edge = (Vertex, Vertex)

pathsWriterT :: [Edge] -> Vertex -> Vertex -> [[Vertex]]
pathsWriterT edges start end = execWriterT (pathsWriterT' edges start end)

pathsWriterT' :: [Edge] -> Vertex -> Vertex -> WriterT [Vertex] [] ()
pathsWriterT' edges start end =
  let e_paths = do (e_start, e_end) <- lift edges
                   guard $ e_start == start
                   tell [start]
                   pathsWriterT' edges e_end end
   in if start == end
      then tell [start] `mplus` e_paths
      else e_paths

let的{​​{1}}和in块中,我告诉编写者将当前顶点添加到路径中。但是后来在pathsWriterT'通过执行作者我得到了可能的路径列表。

Writer如何将所有计算出的路径组合到路径列表中?如何在pathsWriterT表示的单个计算中独立“存储”不同的路径? (原谅我的命令性语言)

1 个答案:

答案 0 :(得分:9)

请记住,Haskell中的Monadm :: * -> *类型,支持两种操作:

  1. return :: a -> m a
  2. (>>=) :: m a -> (a -> m b) -> m b
  3. 虽然考虑do中的一系列操作 - 作为计算的符号通常很有用,但当您对引擎盖下的内容感兴趣时,您应该考虑一下类型m a的值以及涉及return(>>=)时会发生什么。

    有问题的monad是WriterT [Vertex] []。这就是WriterT的定义方式:

    newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
    

    [Vertex]替换为w,将[]替换为m。我们得到了这个:

    [(a, [Vertex])]
    

    所以它是a类型的值列表,每个值都有一个与之关联的顶点列表。这些类型等效 modulo newtype wrap / unwrapping。现在,我们需要了解return(>>=)如何适用于此类型。

    return的{​​p> []创建了一个单例列表。因此return 'x' :: [Char]['x']return WriterT将累加器设置为mempty,并将其余作业委托给内部monad的return

    在我们的例子中,累加器的类型为[Vertex]mempty :: [Vertex][]。这意味着return 'x' :: WriterT [Vertex] [] Char表示为[('x', [])] - 带有空顶点列表的'x'字符。这非常简单:我们monad的return方法创建一个单例列表,其中没有顶点与此列表中的唯一值相关联。

    当然,棘手的部分是(>>=)运算符(发音为&#34; bind&#34;,如果你不知道的话)。对于列表,它的类型为[a] -> (a -> [b]) -> [b]。它的语义是函数a -> [b]将应用于[a]中的每个元素,结果[[b]]将被连接。

    [a, b, c] >>= f将成为f a ++ f b ++ f c。一个简单的例子来说明:

    [10, 20, 30] >>= \a -> [a - 5, a + 5]
    

    你能弄清楚结果列表会是什么吗? (如果没有,请运行GHCi中的示例。)

    没有什么可以阻止您在提供给另一个(>>=)的函数中使用(>>=)

    [10, 20, 30] >>= \a ->
      [subtract 5, (+5)] >>= \f ->
        [f a]
    

    实际上,这就是do - 符号的工作原理。上面的例子相当于:

    do
      a <- [10, 20, 30]
      f <- [subtract 5, (+5)]
      return (f a)
    

    因此,它就像构建一个值树,然后将其展平为一个列表。初始树:

    a <-               (10)-----------------(20)------------------(30)
                        |                     |                     |
                        |                     |                     |
                        v                     v                     v
    f <-   (subtract 5)----(+5)  (subtract 5)----(+5)  (subtract 5)----(+5)
                   |        |            |        |            |        |
                   |        |            |        |            |        |
                   v        v            v        v            v        v
                 [f a]    [f a]        [f a]    [f a]        [f a]    [f a] 
    

    第1步(替换f):

    a <-       (10)-----------------(20)-------------------(30)
                |                     |                     |
                |                     |                     |
                v                     v                     v
        [subtract 5 a, a + 5]  [subtract 5 a, a + 5] [subtract 5 a, a + 5]
    

    第2步(替换a):

    [subtract 5 10, 10 + 5, subtract 5 20, 20 + 5, subtract 5 30, 30 + 5]
    

    然后,当然,它减少到[5, 10, 15, 20, 25, 30, 35]

    现在,您可以记住,WriterT为每个值添加累加器。因此,在展平树的每个步骤中,它将使用mappend来合并这些累加器。

    让我们回到你的例子pathWriterT'。为了便于理解,我将稍微修改一下以删除自循环的处理并使绑定单元显式化:

    pathsWriterT' :: [Edge] -> Vertex -> Vertex -> WriterT [Vertex] [] ()
    pathsWriterT' edges start end
      | start == end = tell [end]
      | otherwise    = do
        (e_start, e_end) <- lift edges
        () <- guard $ e_start == start
        () <- tell [start]
        pathsWriterT' edges e_end end
    

    考虑调用pathsWriterT'其中

    • edges = [(1,2), (2,3), (2,4)]
    • start = 1
    • end = 4

    再次,我们可以绘制一棵树,但它会更加复杂,所以让我们逐行进行:

    {- Line 1 -} (e_start, e_end) <- lift edges
    {- Line 2 -} () <- guard $ e_start == start
    {- Line 3 -} () <- tell [start]
    {- Line 4 -} pathsWriterT' edges e_end end
    

    第1行。edges的类型为[Edge]。当您从lift应用MonadTrans时,它会变为WriterT [Vertex] [] Edge。请记住,简而言之,这只是[(Edge, [Vertex])]lift WriterT的实现非常简单:为每个值设置累加器为mempty。因此,现在我们lift edges等于:

    [ ((1,2), []) ,
      ((2,3), []) ,
      ((2,4), []) ]
    

    我们的树是:

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
    

    对于每个(e_start, e_end)值,会发生以下情况......

    第2行。边的源顶点绑定到e_start,目标顶点绑定到e_endguard在其参数为return ()时展开为True,在其empty时展开为False。对于列表,return ()[()]empty[]。对于我们的monad,我们有相同的但有累加器:return ()[((), [])]empty仍然是[](因为没有值可以将累加器附加到)。由于我们决定start = 1,评估guard的结果是:

    • 代表(1,2)[((), [])]
    • 代表(2,3)[]
    • 代表(2,4)[]

    有三个结果,因为我们正在使用每个元素。让我们将它们添加到我们的树中:

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                 |                 |               |
                                 |                 |               |
                                 v                none            none
    ()               <-      ((), [])
    

    如您所见,我写了none代替(2,3)(2,4)的子节点。因为guard没有为他们提供子节点,所以它返回了一个空列表。现在我们继续......

    第3行。现在我们使用tell来扩展累加器。 tell返回单位值(),但附加了累加器。由于start等于1,因此累加器将为[1]。所以让我们调整我们的树:

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                 |                 |               |
                                 |                 |               |
                                 v                none            none
    ()               <-      ((), [])
                                 |
                                 |
                                 v
    ()               <-      ((), [1])
    

    第4行。现在我们调用pathsWriterT' edges e_end end以递归方式继续构建树!凉。在这个递归调用中:我们有:

    • edges =旧edges
    • start =旧e_end = 2
    • end =旧end = 4

    我们回到第1行。我们的树现在看起来像这样:

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                 |                 |               |
                                 |                 |               |
                                 v                none            none
    ()               <-      ((), [])
                                 |
                                 |
                                 v
    ()               <-      ((), [1])
                                 |
                                 |\_________________________________
                                 |                 |               |
                                 v                 v               v
    (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
    

    再次第2行......只是这一次,它会给我们留下不同的节点(因为start已经改变了)!

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                 |                 |               |
                                 |                 |               |
                                 v                none            none
    ()               <-      ((), [])
                                 |
                                 |
                                 v
    ()               <-      ((), [1])
                                 |
                                 |\_________________________________
                                 |                 |               |
                                 v                 v               v
    (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                 |                 |               |
                                 |                 |               |
                               none                v               v
    ()               <-                        ((), [])        ((), [])
    

    再次第3行,现在它将[2]添加为累加器。

    (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                 |                 |               |
                                 |                 |               |
                                 v                none            none
    ()               <-      ((), [])
                                 |
                                 |
                                 v
    ()               <-      ((), [1])
                                 |
                                 |\_________________________________
                                 |                 |               |
                                 v                 v               v
    (e_start, e_end) <-    ((1,2), [])      ((2,3), [])      ((2,4), [])
                                 |                 |               |
                                 |                 |               |
                               none                v               v
    ()               <-                        ((), [])        ((), [])
                                                   |               |
                                                   |               |
                                                   v               v
    ()               <-                       ((), [2])       ((), [2])
    

    在第4行,我们进入pathsWriterT'

    • edges =旧edges
    • start =旧e_end = 34
    • end =旧end = 4

    请注意,我将34都写为e_end的值。这是因为递归发生在两个分支中:

    1. 在分支(2,3)中,我们将再次创建每个边缘的孩子。
    2. 但是,在分支(2,4)中,请注意start == end成立,结束递归。我们创建了一个小孩[((), [4])],因为这是我们monad的tell [4]的结果。
    3. (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none                v               v
      ()               <-                        ((), [])        ((), [])
                                                     |               |
                                                     |               |
                                                     v               v
      ()               <-                       ((), [2])       ((), [2])
                                                     |               |
                                 ____________________|____           v
                                 |            |          |      [((), [4])]
                                 v            v          v
      (e_start, e_end) <- ((1,2), [])  ((2,3), [])  ((2,4), [])
      

      在第2行,警卫不会让任何新的孩子出现在这里,因为没有节点可以满足e_start == 4

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none                v               v
      ()               <-                        ((), [])        ((), [])
                                                     |               |
                                                     |               |
                                                     v               v
      ()               <-                       ((), [2])       ((), [2])
                                                     |               |
                                 ____________________|____           v
                                 |            |          |      [((), [4])]
                                 v            v          v
      (e_start, e_end) <- ((1,2), [])  ((2,3), [])  ((2,4), [])
                                 |            |          |
                                 |            |          |
                                none         none       none
      ()               <-
      

      呼!我们的树是建造的。现在是减少它的时候了。我会在每个缩小步骤中将树的深度减少1,自下而上。在每个缩减步骤中,我将用其子级的连接列表替换父级,并将父级的累加器mappend替换为其子级的累加器。为什么这个逻辑呢?嗯,这就是为我们的monad定义(>>=)的方式。

      请注意,我们树的叶子的类型为[((), [Vertex])] - 这是pathsWriterT'的返回类型。请记住,none代表空列表[],因此它也具有此类型。内部节点的类型为(a, [Vertex]),其中a是绑定变量的类型(我在树的左侧绘制了变量绑定)。

      第1步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none                v               v
      ()               <-                        ((), [])        ((), [])
                                                     |               |
                                                     |               |
                                                     v               v
      ()               <-                       ((), [2])       ((), [2])
                                                     |               |
                                 ____________________|____           v
                                 |            |          |      [((), [4])]
                                none         none       none
      

      第2步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none                v               v
      ()               <-                        ((), [])        ((), [])
                                                     |               |
                                                     |               |
                                                     v               v
      ()               <-                       ((), [2])       ((), [2])
                                                     |               |
                                                    none             v
                                                                [((), [4])]
      

      第3步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none                v               v
      ()               <-                        ((), [])        ((), [])
                                                     |               |
                                                     |               |
                                                    none             v
                                                               [((), [2,4])]
      

      第4步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                   v                 v               v
      (e_start, e_end) <-    ((1,2), [])      ((2,3), [])     ((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                 none               none             v
                                                               [((), [2,4])]
      

      第5步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
      ()               <-      ((), [1])
                                   |
                                   |\_________________________________
                                   |                 |               |
                                 none               none             v
                                                               [((), [2,4])]
      

      第6步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
      ()               <-      ((), [])
                                   |
                                   |
                                   v
                             [((), [1,2,4])]
      

      第7步。

      (e_start, e_end) <-    ((1,2), [])------((2,3), [])-----((2,4), [])
                                   |                 |               |
                                   |                 |               |
                                   v                none            none
                             [((), [1,2,4])]
      

      第8步。

                             [((), [1,2,4])]
      

      execWriterT会丢弃这些值,只留下累加器,现在我们只剩下[[1,2,4]],这意味着1只有一条路径到4[1,2,4]

      练习:对edges = [(1,2), (1,3), (2,4), (3,4)]执行相同操作(使用笔和纸)。你应该得到[[1,2,4], [1,3,4]]