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.