使用foldr的功能太急切了

时间:2011-11-07 02:41:55

标签: haskell lazy-evaluation

我有一个groupBy的替代实现,这对我来说比Data.List中的版本更有用,因为它不要求测试是等价关系:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' f = foldr step []
    where step x [] = [[x]]
          step x (xs:xss)
              | x `f` head xs = (x:xs):xss
              | otherwise     = [x]:xs:xss

然而,它过于急切,不会开始计算像groupBy' (<) [1,2,3,2,3,4,1,undefined]这样的输入。我已经阅读了HaskellWikiWikibooks文章,这些文章解释了为什么某些事情,比如模式匹配,可以使函数不那么懒,我想我理解那里给出的大多数例子。不过,我不明白为什么这个函数在到达undefined之前无法开始产生输出。模式匹配是否会导致此行为?

由于我刚读过这些文章,可能缺乏经验使我无法将我在那里阅读的内容应用到我的示例代码中。那么,为了表现得更懒,这个特定的实现怎么能改变呢?

3 个答案:

答案 0 :(得分:14)

关键问题是你知道step x xss将始终产生(x:_):_形式的结果,但是你正在“隐藏”这种模式匹配,所以Haskell被迫首先评估它们在它看到那些构造函数之前确定选择step的哪个案例。

通常,要使foldr f x能够在到达列表末尾之前生成任何输出,f必须能够在检查其第二个参数之前生成一些输出。

我们可以通过将step拆分为两个来解决这个问题,这样我们就可以在对第二个参数进行模式匹配之前生成两个(:)构造函数。

groupBy' f = foldr step []
  where step x xss = let (ys, yss) = step' x xss in (x:ys):yss
        step' x [] = ([], [])
        step' x (xs:xss) | f x (head xs) = (xs, xss)
                         | otherwise     = ([], xs:xss)

这就像你可以得到它一样懒惰。

*Main> groupBy' (<) [1, 2, 3, 2, 3, 4, 1, undefined]
[[1,2,3],[2,3,4],[1*** Exception: Prelude.undefined

答案 1 :(得分:4)

foldr step [] [1,2,3,...]将扩展为step 1 (foldr step [] [2,3])。现在step需要决定是进入第一种情况还是第二种情况。为此,它需要知道foldr step [] [2,3,...]是否评估为空列表。为此,它需要知道step 2 (foldr step [] [3,...])是否返回空列表(它永远不会,但Haskell不知道)。这种情况一直持续到列表结束为止(如果列表没有结束,它将永远存在)。

答案 2 :(得分:1)

f不是等价关系时,我很难理解你的代码会做什么,但我想你想要类似下面的代码:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' f [] = []
groupBy' f [x] = [[x]]
groupBy' f (x : xs)
  | x `f` head xs = (x : head l) : tail l
  | otherwise = [x] : l
  where
    l = groupBy' f xs

或等效地不使用headtail

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' f [] = []
groupBy' f (x : xs) = hd : tl
  where
    (hd, tl) = go x xs
    go x [] = ([x], [])
    go x xs@(x' : xs')
      | x `f` x' = (x : hd', tl')
      | otherwise = ([x], hd' : tl')
      where
        (hd', tl') = go x' xs'