Haskell列表推导式脱糖:嵌套推导式的正确转换方法
正确脱糖嵌套列表推导式的方法
问题出在列表Monad的扁平化特性上!你原来的两层do块写法会自动把内层列表的元素平铺到外层列表中,而我们需要的是外层每个x对应一个独立的子列表(哪怕是空列表)。
先理清原列表推导的逻辑
你的原表达式:
[[b|(a,b)<-[(1,"A"),(2,"B")], mod x 2 == 0]|x <- [1..10]]
核心逻辑是:
- 外层遍历
x从1到10 - 对每个
x,生成一个子列表:当x是偶数时,取所有b值;否则返回空列表 - 最终结果是由这些子列表组成的嵌套列表
为什么你的do块写法不对?
你写的两层do块:
do x <- [1..10] do (a,b) <- [(1,"A"),(2,"B")] guard $ mod x 2 == 0 return b
等价于使用列表Monad的>>=(绑定)操作,而列表的>>=本质是concatMap——它会把内层列表的所有元素直接合并到外层列表中,相当于:
concatMap (\x -> concatMap (\(a,b) -> if mod x 2 == 0 then [b] else []) [(1,"A"),(2,"B")]) [1..10]
所以结果被扁平化,变成了一维列表,而不是你想要的嵌套结构。
正确的脱糖写法
要保留嵌套结构,我们需要让外层的每个x直接返回内层列表推导的结果(作为外层列表的一个元素),而不是展开内层元素。可以把内层do块包裹在return里:
do x <- [1..10] return $ do (a,b) <- [(1,"A"),(2,"B")] guard $ mod x 2 == 0 return b
或者用更底层的>>=形式写,逻辑更清晰:
[1..10] >>= \x -> return $ [(1,"A"),(2,"B")] >>= \(a,b) -> guard (mod x 2 == 0) >> return b
验证结果
运行这段代码,你会得到预期的输出:
[[],["A","B"],[],["A","B"],[],["A","B"],[],["A","B"],[],["A","B"]]
本质上,这里外层用的是map(通过return把内层列表作为元素),而不是concatMap,所以保留了嵌套结构。
内容的提问来源于stack exchange,提问作者matt
相关产品推荐
相关产品推荐

