List Haskell-无限列表的列表理解

List Haskell-无限列表的列表理解,list,haskell,List,Haskell,这是一段代码 primepowers n = foldr merge [] [ map (^i) primes | i <- [1..n] ] -- (1) merge::(Ord t) =>[t]->[t]->[t] merge x [] = x merge [] y = y merge (x:xs) (y:ys) | x < y = x:merge xs (y:ys) | otherwise = y:merge (x:xs) ys 但是我们

这是一段代码

primepowers n = foldr merge [] [ map (^i) primes | i <- [1..n] ]  -- (1)

merge::(Ord t) =>[t]->[t]->[t]
merge x [] = x
merge [] y = y
merge (x:xs) (y:ys)
    | x < y = x:merge xs (y:ys)
    | otherwise = y:merge (x:xs) ys
但是我们不应该忘记这些列表是无限的,那么Haskell是如何处理这个事实的呢


有人能给我解释一下上述函数的计算过程吗?

正在合并的列表是无限的,但这并不重要

重要的是要合并的列表数量有限,因此要计算合并的下一个元素,只需执行有限数量的比较

要计算merge xs ys的头,您只需要计算xs的头和ys的头。因此,通过归纳,如果您有一个有限的合并操作树,您可以在有限的时间内计算整个合并的头。

确实,合并需要完全扫描其整个输入列表以生成其整个输出。然而,关键的一点是,输出中的每个元素只依赖于输入列表的有限前缀

例如,考虑取10个map *2(1…)。要计算前10个元素,不需要检查整个[1..]。事实上,map不会扫描整个无限列表,然后开始返回输出:如果它的行为是这样的,它只会挂在无限列表上。map的这种流属性由惰性和map定义给出

map f [] = []
map f (x:xs) = x : map f xs
最后一行读取yield x,然后继续执行其余部分,因此调用方在map生成其整个输出之前检查x。相比之下

map f xs = go xs []
  where go []     acc = acc
        go (x:xs) acc = go xs (acc ++ [f x])
将是map的另一个定义,只有在其输入被消耗之后才开始生成其输出。除了性能,它在有限列表上是等价的,但在无限列表上的无限列表上是不等价的

如果您想根据经验测试您的合并是否确实在惰性地工作,请尝试以下方法:

take 10 $ merge (10:20:30:error "end of 1") (5:15:25:35:error "end of 2")
xs = [x | x <- [1..], error ""]

main = print $ const 0 xs

通过更改常量,您可以自由地玩游戏。您将看到一个异常被打印在屏幕上,但只有在通过merge生成了几个列表元素之后才会出现。

[map^i primes | i技巧是将其定义为

primepowers n = foldr (\(x:xs) r-> x:merge xs r) 
                      [] [ map (^i) primes | i <- [1..n] ]
因为您使用merge作为合并函数。请注意,最左边的merge首先启动,而r的表达式甚至还没有构建,因为还不需要它的值-Haskell的计算是根据需要进行的

现在,这个合并需要它的第一个和第二个参数的头值,实际上它首先检查第二个参数是否为[]

第一个参数不是问题所在,但第二个参数是折叠foldr组合函数中所有剩余列表r的结果,r代表递归结果。因此,将访问列表中的每个元素,并强制其头元素-所有这些只是为了产生一个非常第一个值,即结果列表的头,由最左侧的合并ca我

在我的代码中,合并函数一开始并不需要第二个参数列表的开头。这就限制了它对整个列表的探索,使它的需求更经济,因此如果你完全忽略n,它甚至会更有效


您的示例Haskell表达式[map^i primes | i查看何时计算某些内容的一个好方法是使用Debug.Trace模块。您不能在无限列表上使用foldr,因为它将永远搜索最后一个元素。@DominikDitoIvosevic,这不是真的。请尝试foldr const 0[1..]@DominikDitoIvosevic,作为一些背景,参考。好的。我们有表达式[[2,3,5,7,…],[4,9,25,49,…],[8,27125,…],…]。但是从现在开始,我不理解它。在foldr merge[[2,3,5,7,…],[4,9,25,49,…],[8,27125,…]之后会发生什么?我不明白。Sry:@user3237465折叠的真正好处是我们可以。它可能会迫使更多的内部列表的头部,但总体上应该要快得多。就您的解决方案而言,swap和mergeAll都会使用跨度执行线性搜索,因此将访问列表转换为优先级队列应该是有益的…-Al因此,这里没有重复的代码,如果它们是重复的,您的代码将循环,但是很容易修复:只需使用span@Will Ness,我进行了更多的实验,最快的代码就是primepowers=foldr1\x:xs-rs->x:merge-xs-rs[map^i primes | i great;当然每个问题都有自己的最佳解决方案。对于primes,使用foldi进行树折叠肯定比foldr快得多。也许在这里这并不重要,因为没有重复。-关于@Will Ness,语义上有差异。我读到sprint是一个整洁的工具,我似乎从来没有记得使用过。谢谢提醒一下!
foldr f z  []    = z
foldr f z (x:xs) = f x (foldr f xs)
primepowers n = foldr merge [] [map (^i) primes | i <- [1..3]]
merge thunk1 (merge thunk2 (merge thunk3 []))
merge (2 : thunk1') (merge (4 : thunk2') (8 : thunk3'))
merge (2 : thunk1') (4 : merge thunk2' (8 : thunk3')
2 : merge thunk1' (4 : merge thunk2' (8 : thunk3')
2 : merge (3 : thunk1'') (4 : merge thunk2' (8 : thunk3)
2 : 3 : merge thunk1'' (4 : merge thunk2' (8 : thunk3')
2 : 3 : merge (5 : thunk1''') (4 : merge thunk2' (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (merge thunk2' (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (merge (9 : thunk2'') (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (8 : merge (9 : thunk2'') thunk3')
2 : 3 : 4 : 5 : merge thunk1''' (8 : merge (9 : thunk2'') thunk3')
...
interleave (x:xs) ys = x : interleave ys xs

primepowers = foldr1 interleave [map (^i) primes | i <- [1..]]
[[2,3,5...]
,[4,9,25...]
,[8,27,125...]
...
]
[[5,7,11...]
,[4,9,25...]
,[8,27,125...]
...
]
[[4,9,25...]
,[5,7,11...]
,[8,27,125...]
...
]
[[5,7,11...]
,[9,25,49...]
,[8,27,125...]
...
]
[[5,7,11...]
,[8,27,125...]
,[9,25,49...]
...
]
swap xs@(x:_) xss = xss1 ++ xs : xss2 where
    (xss1, xss2) = span ((< x) . head) xss 

mergeAll (xs:xss@((x:_):_)) = xs1 ++ mergeAll (swap xs2 xss) where
    (xs1, xs2) = span (< x) xs

primepowers = mergeAll [map (^i) primes | i <- [1..]]
primepowers n = foldr (\(x:xs) r-> x:merge xs r) 
                      [] [ map (^i) primes | i <- [1..n] ]
GHCi> let pps_list = [ map (^i) primes | i <- [1..42] ]
GHCi> :sprint pps_list
pps_list = _
GHCi> take 20 $ foldr (\(x:xs) r-> x:merge xs r) [] pps_list
[2,3,4,5,7,8,9,11,13,16,17,19,23,25,27,29,31,32,37,41]
GHCi> :sprint pps_list
pps_list = (2 : 3 : 5 : 7 : 11 : 13 : 17 : 19 : 23 : 29 : 31 : 37 :
            41 : _) :
           (4 : 9 : 25 : 49 : _) : (8 : 27 : 125 : _) : (16 : 81 : _) :
           (32 : 243 : _) : (64 : _) : _
merge ps (merge ps_2 (merge ps_3 (... (merge ps_n [])...)))
= merge ps r
     where r = merge ps_2 (merge ps_3 (... (merge ps_n [])...))
foldr f z (x:xs) = f x (foldr f z xs)