Haskell中do块内单元素列表的收集拼接及执行逻辑疑问
关于Haskell Do块解糖与列表拼接的疑问解答
先贴出你提到的代码:
triples = do [1..] >>= \z -> [1..z] >>= \x -> [x..z] >>= \y -> guard (x^2 + y^2 == z^2) >>= \_ -> return (x, y, z)
你的理解纠正与细节补充
你的步骤理解基本正确,补充两个关键细节:
guard在列表Monad中的行为:断言为真时返回[()](包含空元组的单元素列表),为假时返回[](空列表)return a在列表Monad中等价于[a],就是把值包装成单元素列表
执行顺序:完全对应命令式嵌套循环
这段代码的执行逻辑和命令式语言的嵌套循环完全一致:
- 外层遍历:逐个取出无限列表
[1..]的元素作为z - 中层遍历:针对当前
z,逐个取出[1..z]的元素作为x - 内层遍历:针对当前
x,逐个取出[x..z]的元素作为y - 条件过滤:如果
x² + y² == z²成立,就保留(x,y,z);不成立则直接跳过该组合
单元素列表的拼接:靠>>=的列表定义
要搞懂拼接逻辑,必须明确列表Monad中>>=的底层实现:
(>>=) :: [a] -> (a -> [b]) -> [b] xs >>= f = concat (map f xs)
简单说,>>=会先把列表里的每个元素传给函数f,得到一堆子列表,再把这些子列表扁平化拼接成一个大列表。
放到你的代码里逐层拆解:
- 最内层:当
guard返回[()]时,\_ -> return (x,y,z)会把()映射成[(x,y,z)];如果guard返回[],映射后还是[] - 中层拼接:
[x..z] >>= \y -> ...会把每个y对应的结果列表(要么是单元素三元组列表,要么是空列表)拼接,得到当前x下所有符合条件的三元组 - 上层拼接:
[1..z] >>= \x -> ...会把每个x对应的结果列表拼接,得到当前z下所有符合条件的三元组 - 最外层拼接:
[1..] >>= \z -> ...会把每个z对应的结果列表拼接,最终得到所有勾股三元组的无限列表
关于你测试代码的疑问
你测试的代码省略了最后一步的函数,完整写法应该是:
[1..3] >>= \z -> [1..z] >>= \x -> [x..z] >>= \y -> return (x,y,z)
这时候>>=会把所有(x,y,z)的组合拼接成一个列表,结果是:
[(1,1,1),(1,1,2),(1,2,2),(1,1,3),(1,2,3),(1,3,3),(2,2,3),(2,3,3),(3,3,3)]
而加上guard后,相当于给每个组合加了过滤:只有满足条件的才会生成单元素列表,不满足的生成空列表,最终拼接后就只剩下符合条件的三元组了。
内容的提问来源于stack exchange,提问作者qwertyman9938
相关产品推荐
相关产品推荐

