为什么这个功能没有懒惰?

时间:2012-11-21 04:15:11

标签: haskell lazy-evaluation

我有两个与filtertakeWhile类似的功能。

filterAcc, takeWhileAcc :: ([a] -> Bool) -> [a] -> [a]
filterAcc p xs = go xs []
    where go [] acc     = acc
          go (x:xs) acc
            | p (x:acc) = go xs (x:acc)
            | otherwise = go xs acc

takeWhileAcc p xs = go xs []
    where go [] acc     = acc
          go (x:xs) acc
            | p (x:acc) = go xs (x:acc)
            | otherwise = acc

它们都采用谓词和列表,它们与常规filtertakeWhile不同,因为谓词将累积结果作为输入。

我的问题是,filter even [1..]会立即(懒惰地)开始生成输出,filterAcc (any even) [1..]会挂起。我怀疑帮助函数go阻止这些函数懒散地行动。

如何让这些功能懒散地运作?

2 个答案:

答案 0 :(得分:6)

问题在于go的缺点总是以对自身的尾调用结束。当它到达列表的末尾时,它只返回一些有用的东西,当然,它永远不会出现在无限列表中。

相反,您应该随时返回元素:

filterAcc, takeWhileAcc :: ([a] -> Bool) -> [a] -> [a]
filterAcc p xs = go xs []
    where go [] acc     = []
          go (x:xs) acc
            | p (x:acc) = x : go xs (x:acc)
            | otherwise = go xs acc

takeWhileAcc p xs = go xs []
    where go [] acc     = []
          go (x:xs) acc
            | p (x:acc) = x : go xs (x:acc)
            | otherwise = []

答案 1 :(得分:1)

懒惰列表消费通常由foldr实现。

您的累加器需要从左到右的信息流。这通常通过使用foldl来实现,但这意味着严格的列表消耗。

解决方案是使用scanl

--- mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
--- scanl     :: (a   -> b -> a)        -> a   -> [b] -> [a]

takeWhileAcc p []     = []
takeWhileAcc p (x:xs) = map snd $ takeWhile (p.fst) 
                                $ scanl (\(acc,_) y-> (y:acc,y)) ([x],x) xs

filterAcc p []        = []
filterAcc p (x:xs)    = map snd $ filter (p.fst) 
                                $ scanl (\(acc,_) y-> (y:acc,y)) ([x],x) xs

另一种可能性是使用untilmapAccumL。后者将是一个自然的拟合,除了它不收集累积值,而是传递最后一个累加器值。