Range Over Iterators in Haskell

Starting with version 1.23, the language has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example we had an AllElements method that returned a slice of all elements in the list. With iterators, we can do it better - as shown below.

data List a = List { head :: Maybe (Element a), tail :: Maybe (Element a) }

data Element a = Element { next :: Maybe (Element a), val :: a }

push :: List a -> a -> List a
push lst v = case tail lst of
    Nothing -> lst { head = Just newElem, tail = Just newElem }
    Just tl -> lst { tail = Just newElem, head = (head lst) { next = Just newElem } }
  where
    newElem = Element { next = Nothing, val = v }

The all function returns an iterator, which in this language is a function with a special signature.

all :: List a -> [a]
all lst = iterateElements (head lst)
  where
    iterateElements Nothing = []
    iterateElements (Just el) = val el : iterateElements (next el)

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as the condition is met.

genFib :: [Int]
genFib = map fst $ iterate (\(a, b) -> (b, a + b)) (1, 1)

Now, the main function demonstrates how to use these iterators.

main :: IO ()
main = do
    let lst = push (push (push (List Nothing Nothing) 10) 13) 23
    print $ all lst

    let allElems = all lst
    putStrLn $ "all: " ++ show allElems

    let fib = takeWhile (< 10) genFib
    mapM_ print fib

Output when running this program:

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8

Here, the List.All returns an iterator, which is used in a regular list comprehension or a similar iteration construct.

Now that we can use and build iterators, let’s learn more about the language.