了解filterM

时间:2015-03-05 07:36:48

标签: haskell monads

考虑

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 -> [True, False]),第一个x应该是1。那么flg <- p x会返回什么?每次递归flg的确切值是什么?

2 个答案:

答案 0 :(得分:20)

列表monad []模型非确定性:值列表[a]表示a值的多种不同可能性。

当您在列表monad中看到flg <- p x之类的语句时,flg将依次采用p x的每个值,即True,然后False } 在这种情况下。然后filterM的其余部分执行两次,每次flg的值一次。

要了解如何更详细地发生这种情况,您需要了解do符号的贬低以及列表monad的(>>=)运算符的实现。

do符号逐行进入(>>=)运算符的调用。例如,非空filterM案例的主体变为

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

这完全是机械的,因为它实质上只是在表达式之后用flg <-替换>>= \flg ->之前的(>>=)。实际上,模式匹配使这更复杂,但并不多。

接下来是Monad的实际实现,[](>>=) :: [a] -> (a -> [b]) -> [b] 类型类的成员,并且每个实例都有不同的实现。对于[] >>= f = [] (x:xs) >>= f = f x ++ (xs >>= f) ,类型为:

(>>=)

并且实现类似于

do

所以循环发生在(>>=)的主体中。这完全在一个库中,除了 xs >>= f = concat (map f xs) 符号的消失之外没有编译器魔法。

filterM的等效定义是

flg

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

return的递归调用发生同样的事情:对于ys的每个值,进行递归调用并生成结果列表,并生成最终的2^3 = 8语句对于此结果列表中的每个元素filterM (\x -> [True, False]) [1, 2, 3]执行。

这&#34;扇出&#34;在每次递归调用中,{{1}}的最终结果中会显示{{1}}个元素。

答案 1 :(得分:0)

这很简单,在我们将所有内容都记录下来之后(有人聪明地提出了这样的建议:不要试图将所有操作都记在纸上!):

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])