Search code examples
haskelllazy-evaluation

Why does my function not work with an infinite list?


I'm trying to learn haskell and implemented a function conseq that would return a list of consecutive elements of size n.

conseq :: Int -> [Int] -> [[Int]]
conseq n x 
      | n == length(x) = [x]
      | n > length(x) = [x]
      | otherwise = [take n x] ++ (conseq n (drop 1 x))

This works correctly.

> take 5 $ conseq 2 [1..10]  
[[1,2],[2,3],[3,4],[4,5],[5,6]]

However, if I pass [1..] instead of [1..10], the program gets stuck in an infinite loop.

As I understood it, haskell has lazy evaluation so I should still be able to get the same result right? Is it length? Shouldn't the first two conditions evaluate to false as soon as the length becomes greater than n?

What did I misunderstand?


Solution

  • One of the main reasons why using length is not a good idea is because when it has to be evaluated on an infinite list, it will get stuck in an infinite loop.

    The good news is however, we don't need length. It would also make the time complexity worse. We can work with two enumerators, one is n-1 places ahead of the other. If this enumerator reaches the end of the list, then we know that the first enumerator still has n-1 elements, and thus we can stop yielding values:

    conseq :: Int -> [a] -> [[a]]
    conseq n ys = go (drop (n-1) ys) ys
        where go [] _ = []
              go (_:as) ba@(~(_:bs)) = take n ba : go as bs

    This gives us thus:

    Prelude> conseq 3 [1 ..]
    [[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10],[9,10,11],[10,11,12],[11,12,13],[12,13,14],[13,14,15],[14,15,16],[15,16,17],[16,17,18],[17,18,19],[18,19,20],[19,20,21],[20,21,22],[21,22,23],[22,23,24],[23,24,25],[24,25,26],[25,26,27],…
    Prelude> conseq 3 [1 .. 4]
    [[1,2,3],[2,3,4]]