上面的表达式可能不是 Haskell 所认为的 惯用。可能更好的版本是:
[ <b>(x0, x1)</b> | <b>(x0:x1:_)</b> <- mapM (const "ABC") [1..2], <b>x0 < x1</b> ]
这样更简洁,如果mapM (const "ABC") 中的列表将返回包含少于两个元素的列表(这在此处是不可能的),则不会出错。
这里的核心问题可能是了解mapM 的工作原理。表达式mapM (const "ABC") [1..2] 归结为:
mapM (\_ -> "ABC") [1..2]
由于我们不考虑列表的值,所以相当于:
replicateM 2 "ABC"
2 的 replicateM 可以重写为(伪 Haskell 语法):
replicateM 2 :: [a] -< [[a]]
replicateM 2 l = do
x0 <- l
x1 <- l
return [x0, x1]
或者如果我们去糖:
replicateM 2 l = l >>= \x0 -> l >>= \x1 -> return [x0, x1]
对于列表,Monad 的实例实现为:
instance Monad [] where
return x = [x]
(>>=) = flip concatMap
这意味着对于列表,此replicateM 2 实现为:
replicateM 2 l :: [a] -> [[a]]
replicateM 2 l = concatMap (\x0 -> concatMap (\x1 -> [[x0, x1]]) l) l
或更简单:
replicateM 2 l :: [a] -> [[a]]
replicateM 2 l = concatMap (\x0 -> map (\x1 -> [x0, x1]) l) l
因此,我们对列表中的两个项目进行了所有可能的组合。这意味着:
Prelude Control.Monad> replicateM 2 "ABC"
["AA","AB","AC","BA","BB","BC","CA","CB","CC"]
然后我们在列表推导中使用它,对于每个包含两个元素的子列表,我们检查第一个元素 x0 是否小于列表中具有 filter 部分的第二个元素理解(x0 < x1)。如果是这种情况,我们将这些元素生成为 2 元组。
因此,如果第一个元素(严格)小于第二个元素,则它为 "ABC" 中的每两个项目创建 2 元组。
然而,在这里我们做了一些“太多的工作”:超过一半的元素将被拒绝。我们可以通过使用tails :: [a] -> [[a]]来优化它:
import Data.List(tails)
[(x0, x1) | (x0:xs) <- tails "ABC", x1 <- xs ]
产生相同的值:
Prelude Control.Monad Data.List> [(x0, x1) | (x0:xs) <- tails "ABC", x1 <- xs ]
[('A','B'),('A','C'),('B','C')]