如何实现惰性常量空间三分区函数?

时间:2012-02-01 09:39:00

标签: haskell lazy-evaluation ghc

我已经概括了现有的Data.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)

到“三分区”功能

ordPartition :: (a -> Ordering) -> [a] -> ([a],[a],[a])
ordPartition cmp xs = foldr select ([],[],[]) xs
  where
    -- select :: a -> ([a], [a], [a]) -> ([a], [a], [a])
    select x ~(lts,eqs,gts) = case cmp x of
        LT -> (x:lts,eqs,gts)
        EQ -> (lts,x:eqs,gts)
        GT -> (lts,eqs,x:gts)

但是现在我在使用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

所以现在我的问题是,

  1. doo中空间泄漏的原因是什么,这似乎让我感到惊讶,因为foobar没有表现出这样的空间泄漏?以及

  2. 有没有办法以这样的方式实现ordPartition,当在doo等函数的上下文中使用时,它会以恒定的空间复杂度执行?

1 个答案:

答案 0 :(得分:5)

这不是空间泄漏。要确定组件列表是否为空,必须遍历整个输入列表,并构建其他组件列表(如果是thunk)。在doo情况下,xs1为空,因此在决定输出内容之前必须构建整个事物。

这是所有分区算法的基本属性,如果其中一个结果为空,并且您检查其空白作为条件,则在遍历整个列表之前无法完成该检查。