任何顺序或维度的 Haskell 列表理解

List comprehension in Haskell of any order or dimension

考虑以下程序。

chars = [" "] ++ ["A"] ++ ["B"] ++ (repeat "ABCD")

f :: Int -> [(Char,Int)]
f n = (,) <$> (chars !! n) <*> [1..3]

g :: Int -> [[(Char,Int)]]
g 1 = (\a     -> [a    ]) <$> (f 1)
g 2 = (\a b   -> [a,b  ]) <$> (f 1) <*> (f 2)
g 3 = (\a b c -> [a,b,c]) <$> (f 1) <*> (f 2) <*> (f 3)
-- g n = (\x1 x2 ... xn -> [x1,x2,...,xn]) <$> (f 1) <*> (f 2) <*> ... (f n)

我们如何为所有 n > 0 编写 g n,而不像上面那样明确地输入扩展,理想情况下只使用 Prelude(如果需要,还可以使用 Control.Applicative)?请注意,对于所有 n>3,f n = f (n-1),因此可以递归地定义 g

输出结果是这样的(忽略漂亮的打印):

> g 1
[ [ ( 'A' , 1 ) ] , [ ( 'A' , 2 ) ] , [ ( 'A' , 3 ) ] ]

> g 2
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 3 ) ]
]

> g 3
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 2 ) ]
...
, [ ( 'A' , 3 ) , ( 'B' , 3 ) , ( 'D' , 3 ) ]
]

像这样:

g 0 = [[]]
g n = (\xs x -> xs ++ [x]) <$> g (n - 1) <*> f n

或者更复杂,但也更高效:

g = map ($ []) . go
  where
    go :: Int -> [[(Char,Int)] -> [(Char,Int)]]
    go 0 = [id]
    go n = (\xs x -> xs . (x:)) <$> go (n - 1) <*> f n

这使用 Hughes lists/difference 列表来避免 \xs x -> xs ++ [x]the quadratic slowdown

g n = traverse f [1 .. n]

traverse 处于序曲中(至少最近几年是这样)。

因为这有点不明显,所以我是这样的:

  1. 我注意到您将 f 应用于从 1 到 n 的数字,所以我从 map f [1 .. n] 开始。

  2. 这产生了一个 [[(Char, Int)]],这是所需的结果类型,但它需要某种......转向侧面并相乘。您需要来自内部列表中非确定性值选择的所有列表。非确定性选择是 []Applicative 实例的本质,事实证明 [[a]] 类型的 sequence 正是操作 "produce all the lists you get from combinatorially combining elements from the inner lists" .这让我 sequence $ map f [1 .. n].

  3. 但是 sequencemap 这对很常见,所以有一个操作可以同时执行这两个操作。 sequence . map f === traverse f。因此应用该规则简化了结果。 traverse f [1 .. n].

g n = mapM f [1..n]

如何到达那里。

您的示例等效地使用列表理解语法编写为

g :: Int -> [[(Char,Int)]]
g 1 = [[a    ] | a <- (f 1)]
g 2 = [[a,b  ] | a <- (f 1), b <- (f 2)]
g 3 = [[a,b,c] | a <- (f 1), b <- (f 2), c <- (f 3)]
-- g n = [[a,b,c, ... , z] | a <- (f 1), b <- (f 2), ... , z <- (f n)]

这正是 sequence 的定义方式,在 Monad Comprehension 语法中:

sequence [as,bs,cs, ... , zs] = [[a,b,c, ... , z] | a <- as, b <- bs, ... , z <- zs]

因此

g n = sequence [(f 1), (f 2), ... , (f n)]
    = sequence $ map f [1..n]

这又是 mapM.

的定义(或其等价物)

(mapM 现在也被称为 traverse,对于也是应用程序的类型,以及 Monad。Haskell 列表 [] 无论如何,而且我发现名称 mapM 更清晰,更便于输入)。


这是一个 public 服务公告,支持被低估的 List Comprehension 和 Monad Comprehension 语法。

我实际上对 g n = sequence $ map f [1..n] 没意见,并且不觉得有任何进一步缩短它的特别冲动,因为我发现它比这两种选择都更清晰。