Haskell 如何实现惰性常数空间三分函数?
我已经概括了现有的Haskell 如何实现惰性常数空间三分函数?,haskell,lazy-evaluation,ghc,Haskell,Lazy Evaluation,Ghc,我已经概括了现有的数据.List.partition实现 partition :: (a -> Bool) -> [a] -> ([a],[a]) partition p xs = foldr (select p) ([],[]) xs where -- select :: (a -> Bool) -> a -> ([a], [a]) -> ([a], [a]) select p x ~(ts,fs) | p x = (x
数据.List.partition
实现
partition :: (a -> Bool) -> [a] -> ([a],[a])
partition p xs = foldr (select p) ([],[]) xs
where
-- select :: (a -> Bool) -> a -> ([a], [a]) -> ([a], [a])
select p x ~(ts,fs) | p x = (x:ts,fs)
| otherwise = (ts, x:fs)
到一个“三分区”函数
但是现在我在使用ghc-O1
编译时遇到了一个令人困惑的行为,'foo'和'bar'函数在恒定空间中工作,但是doo
函数会导致空间泄漏
foo xs = xs1
where
(xs1,_,_) = ordPartition (flip compare 0) xs
bar xs = xs2
where
(_,xs2,_) = ordPartition (flip compare 0) xs
-- pass-thru "least" non-empty partition
doo xs | null xs1 = if null xs2 then xs3 else xs2
| otherwise = xs1
where
(xs1,xs2,xs3) = ordPartition (flip compare 0) xs
main :: IO ()
main = do
print $ foo [0..100000000::Integer] -- results in []
print $ bar [0..100000000::Integer] -- results in [0]
print $ doo [0..100000000::Integer] -- results in [0] with space-leak
所以我现在的问题是,
foo
和bar
没有出现这样的空间泄漏,我觉得奇怪的是doo
中的空间泄漏是什么原因?及ordPartition
的方法,当在doo
等函数的上下文中使用时,它以恒定的空间复杂度执行这不是空间泄漏。要确定组件列表是否为空,必须遍历整个输入列表,如果为空,则构建其他组件列表(如thunks)。在
doo
的情况下,xs1
是空的,所以在决定输出什么之前必须构建整个东西
这是所有分区算法的一个基本属性,如果其中一个结果为空,并且作为一个条件检查其是否为空,则在遍历整个列表之前,该检查无法完成。谢谢,但是我有一个额外的问题仍然让我困惑:如果我有一个定义为
baz xs=(xs1,xs2)的baz xs=(xs1,xs2,xs3)=ordPartition(翻转比较0)xs
,为什么需要保留\u xs3
(当foo
和bar
不需要时)的值呢。由于结果决不取决于xs3
,因此这一点可以忽略。选择器thunk优化可能不会启动,并且通过保持活动的元组引用了\u xs3
,但这是一个意外。doo
中的问题是需要xs1
来找出结果是什么。在这个问题得到回答之前,任何东西都不能因为可能需要而放弃。而且,如本例中所示,如果在处理所有内容之前无法回答该问题,则必须保留结果可能需要的所有内容;如果我将(,)
构造函数更改为(++)
应用程序,或者如果我将bang模式添加到xs1
和xs2
中,我看到的thunk将被避免。。。这值得作为ghc bug报告报告吗?报告过多比未报告的bug要好。但是已经有一些与此相关的票证,所以您可以搜索并查看是否最好对现有票证作出响应。但是,如果是回归,一定要打开一张新票。@is7s-O2
在这里没有区别;我想部分原因是(a,b)
仍然是由两个惰性值a
和b
组成的,不清楚以后实际需要哪个值。。。
foo xs = xs1
where
(xs1,_,_) = ordPartition (flip compare 0) xs
bar xs = xs2
where
(_,xs2,_) = ordPartition (flip compare 0) xs
-- pass-thru "least" non-empty partition
doo xs | null xs1 = if null xs2 then xs3 else xs2
| otherwise = xs1
where
(xs1,xs2,xs3) = ordPartition (flip compare 0) xs
main :: IO ()
main = do
print $ foo [0..100000000::Integer] -- results in []
print $ bar [0..100000000::Integer] -- results in [0]
print $ doo [0..100000000::Integer] -- results in [0] with space-leak