了解过滤器M
Understanding filterM
考虑
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
return 是什么意思呢?每次递归 flg
的值到底是多少?
列表单子[]
模型非确定性:值列表[a]
表示值[=16的多种不同可能性=].
当你在列表 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
类型 class 的成员,每个实例都有不同的实现。对于[]
,类型是:
(>>=) :: [a] -> (a -> [b]) -> [b]
实现类似于
[] >>= f = []
(x:xs) >>= f = f x ++ (xs >>= f)
所以循环发生在 (>>=)
的正文中。这一切都在一个库中,除了 do
符号的脱糖之外没有编译器魔法。
(>>=)
的等效定义是
xs >>= f = concat (map f xs)
这也可以帮助您了解正在发生的事情。
对 filterM
的递归调用也会发生同样的事情:对于 flg
的每个值,都会进行递归调用并生成结果列表,最后的 return
语句针对此结果列表中的每个元素 ys
执行。
每个递归调用的 "fan-out" 在 filterM (\x -> [True, False]) [1, 2, 3]
的最终结果中产生 2^3 = 8
个元素。
这很简单,在我们把它全部记在纸上之后(曾经有人聪明地给出过这样的建议:不要尝试在脑子里全部做,把它全部记在纸上!):
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])
考虑
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
return 是什么意思呢?每次递归 flg
的值到底是多少?
列表单子[]
模型非确定性:值列表[a]
表示值[=16的多种不同可能性=].
当你在列表 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
类型 class 的成员,每个实例都有不同的实现。对于[]
,类型是:
(>>=) :: [a] -> (a -> [b]) -> [b]
实现类似于
[] >>= f = []
(x:xs) >>= f = f x ++ (xs >>= f)
所以循环发生在 (>>=)
的正文中。这一切都在一个库中,除了 do
符号的脱糖之外没有编译器魔法。
(>>=)
的等效定义是
xs >>= f = concat (map f xs)
这也可以帮助您了解正在发生的事情。
对 filterM
的递归调用也会发生同样的事情:对于 flg
的每个值,都会进行递归调用并生成结果列表,最后的 return
语句针对此结果列表中的每个元素 ys
执行。
每个递归调用的 "fan-out" 在 filterM (\x -> [True, False]) [1, 2, 3]
的最终结果中产生 2^3 = 8
个元素。
这很简单,在我们把它全部记在纸上之后(曾经有人聪明地给出过这样的建议:不要尝试在脑子里全部做,把它全部记在纸上!):
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])