Outermost reduction permits the definition of infinite data structures. For instance, the list of all integers starting at n is given by the function
listfrom n = n: (listfrom (n+1))
The output of listfrom m is the infinite list [m, m+1, ... which is denoted [m..] in Haskell.
We can use infinite lists, for instance, to define in a natural way the Sieve of Eratosthenes whose output is the (infinite) list of all prime numbers.
sieve (x:xs) = x : (sieve [ y <- xs | mod y x != 0]) primes = sieve [2..]
The function sieve picks up the first number of its input list, constructs a new list from its input by removing all multiples of the first number and then recursively runs sieve on this list.
If we work out the reduction for this we get
primes | sieve [2..] | |||
2 : (sieve [ y | y <- [3..] , mod y 2 != 0]) | ||||
2 : (sieve (3 : [y | y <- [4..], mod y 2 != 0]) | ||||
2 : (3 : | ||||
(sieve [z
|
||||
2 : (3 : (5 : | ||||
(sieve [w | w <- (sieve [z | z <- (sieve [y | y <- [4..], | ||||
mod y 2 != 0]) | mod z 3 != 0]) | mod w 5 != 0]) | ||||
... |
Why is this useful? It is often conceptually easier to define a function that returns an infinite list and extract a finite prefix to get a concrete value.
For instance, from primes we can derive functions such as
nthprime k = primes !! k
that extracts the kth prime number.