【问题标题】:Understanding filterM了解 filterM
【发布时间】:2015-03-05 07:36:47
【问题描述】:

考虑

filterM (\x -> [True, False]) [1, 2, 3]

我无法理解 Haskell 对这个 filterM 用例的神奇之处。该函数的源代码如下:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
    flg <- p x
    ys  <- filterM p xs
    return (if flg then x:ys else ys)

在这个用例中,p 应该是 lambda 函数 (\x -&gt; [True, False]),第一个 x 应该是 1。那么flg &lt;- p x 返回什么?每次递归时flg 的值究竟是多少?

【问题讨论】:

  • 这个问题非常相似:stackoverflow.com/questions/25476248/powerset-function-1-liner,但它并没有过多关注filterM 的主体,所以我认为它不是完全重复的。
  • filterM (\x-&gt;[True,False]) xs 大致意思是:对于xs 的每个元素,不确定地取它还是丢弃它。结果应该是xs的所有子列表的列表(长度为2 ^ length xs)。

标签: haskell monads


【解决方案1】:

列表单子[] 模型非确定性:值列表[a] 代表a 值的许多不同可能性。

当您在列表 monad 中看到类似 flg &lt;- p x 的语句时,flg 将依次采用 p x 的每个值,即 True,然后是 False。然后filterM 的其余部分被执行两次,每个flg 的值执行一次。

要更详细地了解这是如何发生的,您需要了解 do 符号的脱糖以及列表 monad 的 (&gt;&gt;=) 运算符的实现。

do 表示法在对(&gt;&gt;=) 运算符的调用中逐行脱糖。例如非空filterM case 的主体变成

p x >>= \flg -> (filterM p xs >>= \ys -> return (if flg then x:ys else ys))

这完全是机械的,因为它本质上只是将表达式之前的flg &lt;- 替换为表达式之后的&gt;&gt;= \flg -&gt;。实际上,模式匹配会使这变得更复杂一些,但并不复杂。

接下来是(&gt;&gt;=)的实际实现,它是Monad类型类的成员,每个实例都有不同的实现。对于[],类型为:

(>>=) :: [a] -> (a -> [b]) -> [b]

实现类似于

[] >>= f = []
(x:xs) >>= f = f x ++ (xs >>= f)

所以循环发生在(&gt;&gt;=) 的主体中。这一切都在一个库中,除了 do 符号的脱糖之外,没有任何编译器魔法。

(&gt;&gt;=) 的等效定义是

 xs >>= f = concat (map f xs)

这也可以帮助您了解正在发生的事情。

对于filterM 的递归调用也会发生同样的事情:对于flg 的每个值,进行递归调用并生成一个结果列表,并为每个元素@ 执行最终的return 语句987654351@ 在这个结果列表中。

每次递归调用的这种“扇出”会导致filterM (\x -&gt; [True, False]) [1, 2, 3] 的最终结果中的2^3 = 8 元素。

【讨论】:

  • @xingmu1978:实际上,这就是instance Monad []&gt;&gt;= 背后的魔力,所以如果您对细节感兴趣,请查看list monad 教程。
  • p x >>= \flg -> (filterM p xs >>= \ps -> return (if flg then x:ys else ys)) 'ps' 应该是 'ys',对吗?
【解决方案2】:

这很简单,在我们将所有内容都写在纸上之后(曾经有一位聪明人给出了这样的建议:不要试图在头脑中做所有事情,将其全部写在纸上!):

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do { flg <- p x
                       ; ys  <- filterM p xs
                       ; return (if flg then x:ys else ys) }

-- filterM (\x -> [True, False]) [1, 2, 3]
g [x,y,z] = filterM (\x -> [True, False]) (x:[y,z])
          = do {
                 flg <- (\x -> [True, False]) x
               ; ys  <- g [y,z]
               ; return ([x | flg] ++ ys) }
         = do {
                flg <- [True, False]               -- no `x` here!
              ; ys  <- do { flg2 <- (\x -> [True, False]) y
                          ; zs  <- g [z]
                          ; return ([y | flg2] ++ zs) }
              ; return ([x | flg] ++ ys) }
         = do {
                flg  <- [True, False]
              ; flg2 <- [True, False]
              ; zs   <- do { flg3 <- (\x -> [True, False]) z
                           ; s  <- g []
                           ; return ([z | flg3] ++ s) }
              ; return ([x | flg] ++ [y | flg] ++ zs) }
         = do {
                flg  <- [True, False]
              ; flg2 <- [True, False]
              ; flg3 <- [True, False]
              ; s    <- return []
              ; return ([x | flg] ++ [y | flg2] ++ [z | flg3] ++ s) }

嵌套 do 块的取消嵌套遵循 Monad 定律。

因此,在伪代码中:

    filterM (\x -> [True, False]) [1, 2, 3]
    =
      for flg in [True, False]:    -- x=1
          for flg2 in [True, False]:     -- y=2
              for flg3 in [True, False]:     -- z=3
                  yield ([1 | flg] ++ [2 | flg2] ++ [3 | flg3])

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-21
    • 2010-11-12
    • 2020-09-18
    • 2011-01-07
    • 2016-07-15
    • 2012-02-28
    相关资源
    最近更新 更多