任何顺序或维度的 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
处于序曲中(至少最近几年是这样)。
因为这有点不明显,所以我是这样的:
我注意到您将 f
应用于从 1 到 n 的数字,所以我从 map f [1 .. n]
开始。
这产生了一个 [[(Char, Int)]]
,这是所需的结果类型,但它需要某种......转向侧面并相乘。您需要来自内部列表中非确定性值选择的所有列表。非确定性选择是 []
的 Applicative
实例的本质,事实证明 [[a]]
类型的 sequence
正是操作 "produce all the lists you get from combinatorially combining elements from the inner lists" .这让我 sequence $ map f [1 .. n]
.
但是 sequence
和 map
这对很常见,所以有一个操作可以同时执行这两个操作。 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]
没意见,并且不觉得有任何进一步缩短它的特别冲动,因为我发现它比这两种选择都更清晰。
考虑以下程序。
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
处于序曲中(至少最近几年是这样)。
因为这有点不明显,所以我是这样的:
我注意到您将
f
应用于从 1 到 n 的数字,所以我从map f [1 .. n]
开始。这产生了一个
[[(Char, Int)]]
,这是所需的结果类型,但它需要某种......转向侧面并相乘。您需要来自内部列表中非确定性值选择的所有列表。非确定性选择是[]
的Applicative
实例的本质,事实证明[[a]]
类型的sequence
正是操作 "produce all the lists you get from combinatorially combining elements from the inner lists" .这让我sequence $ map f [1 .. n]
.但是
sequence
和map
这对很常见,所以有一个操作可以同时执行这两个操作。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]
没意见,并且不觉得有任何进一步缩短它的特别冲动,因为我发现它比这两种选择都更清晰。