Do块的正确脱糖验证:我的Bind写法及理解是否正确?
Haskell Do块脱糖与Bind操作的列表结构理解
原始Do块代码
triples = do z <- [1..] x <- [1..z] y <- [x..z] guard (x^2 + y^2 == z^2) return (x, y, z) guard :: Bool -> [()] guard True = [()] guard False = []
手动脱糖后的Bind写法
triples = [1..] >>= \z -> [1..z] >>= \x -> [x..z] >>= \y -> guard(x^2 + y^2 == z^2) >>= \_ -> return(x,y,z)
问题
我的理解是,该代码会生成类似[[[[(x,y,z)], [(x2,y2,z2)],[(xn,yn,zn)]]]]的多层嵌套列表,Bind操作会逐层拼接这些列表。请问这个理解是否正确?
解答
你的理解不完全准确。
要搞清楚这个问题,得先明确Haskell中列表类型的>>=(Bind)操作定义:对于列表xs和函数f :: a -> [b],xs >>= f等价于concat (map f xs)——简单说就是先把列表里每个元素通过f映射成新的列表,再把所有这些子列表直接拼接成一个单层列表。
回到你的代码逐层级分析:
- 最外层
[1..] >>= \z -> ...:对每个z执行后续逻辑得到一个列表,然后把所有z对应的列表拼接成一个大列表。 - 针对每个z,
[1..z] >>= \x -> ...:对每个x执行后续逻辑得到列表,再拼接成该z对应的单层列表。 - 同理,
[x..z] >>= \y -> ...也是把每个y对应的结果拼接成单层列表。 guard ... >>= \_ -> return (x,y,z):当guard条件为True时,guard返回[()],Bind操作会直接取return (x,y,z)生成的[(x,y,z)];当guard为False时,guard返回[],Bind后的结果也是[],相当于过滤掉不满足条件的三元组。
整个过程不会产生多层嵌套的列表,最终triples是一个单层的无限列表,里面按顺序排列着所有满足勾股定理的三元组,比如(3,4,5), (6,8,10), (5,12,13), ...。
你误以为会有多层嵌套,是混淆了Bind的作用——列表的Bind本质就是用来“压平”嵌套结构的,它的核心操作是concat,直接把所有子列表合并成一层,不会保留嵌套层级。
内容的提问来源于stack exchange,提问作者qwertyman9938
相关产品推荐
相关产品推荐

