考虑
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
的确切值是什么?
答案 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])