Gentle Introduction to Haskell 98, Online Supplement
Part 9
Covers Sections 3.3, 3.4, 3.5
> module Part9() where
> import Prelude hiding (take,zip)
Section: 3.3 Functions are Non-strict
Observing lazy evaluation can present difficulties. The essential
question is `does an expression get evaluated?'. While in theory using a
non-terminating computation is the way evaluation issues are examined,
we need a more practical approach. In expressions, Haskell uses `error'
to denote bottom. Evaluation of `error' will halt execution and print the
attached error message. The error function can be used to create stub
functions for program components which have not been written yet or
as a value to insert into data structures where a data value is
required but should never be used.
> e1 :: Bool -- This can be any type at all!
> e1 = error "e1" -- evaluate this and see what happens.
> const1 :: a -> Int
> const1 x = 1
> e2 :: Int
> e2 = const1 (error "e2") -- The bottom (the error function) is not
> -- needed and will thus not be evaluated.
Section: 3.4 "Infinite" Data Structures
Data structures are constructed lazily. A constructor like : will not
evaluate its arguments until they are demanded. All demands arise from
the need to print the result of the computation -- components not needed
to compute the printed result will not be evaluated.
> list1 :: [Int]
> list1 = (1:error "list1")
> e3 = head list1 -- does not evaluate the error
> e4 = tail list1 -- does evaluate error
Some infinite data structures. Don't print these! If you do, you will
need to interrupt the system or kill the Haskell process.
> ones :: [Int]
> ones = 1 : ones
> numsFrom :: Int -> [Int]
> numsFrom n = n : numsFrom (n+1)
An alternate numsFrom using series notation:
> numsFrom' :: Int -> [Int]
> numsFrom' n = [n..]
> squares :: [Int]
> squares = map (^2) (numsFrom 0)
Before we start printing anything, we need a function to truncate these
infinite lists down to a more manageable size. The `take' function
extracts the first k elements of a list:
> take :: Int -> [a] -> [a]
> take 0 x = [] -- two base cases: k = 0
> take k [] = [] -- or the list is empty
> take k (x:xs) = x : take (k-1) xs
now some printable lists:
> e5 :: [Int]
> e5 = take 5 ones
> e6 :: [Int]
> e6 = take 5 (numsFrom 10)
> e7 :: [Int]
> e7 = take 5 (numsFrom' 0)
> e8 :: [Int]
> e8 = take 5 squares
zip is a function which turns two lists into a list of 2 tuples. If
the lists are of differing sizes, the result is as long as the
shortest list.
> zip (x:xs) (y:ys) = (x,y) : zip xs ys
> zip xs ys = [] -- one of the lists is []
> e9 :: [(Int,Int)]
> e9 = zip [1,2,3] [4,5,6]
> e10 :: [(Int,Int)]
> e10 = zip [1,2,3] ones
> fib :: [Int]
> fib = 1 : 1 : [x+y | (x,y) <- zip fib (tail fib)]
> e11 = take 10 fib
This can be done without the list comprehension:
> fib' :: [Int]
> fib' = 1 : 1 : map (\(x,y) -> x+y) (zip fib (tail fib))
This could be written even more cleanly using a map function which
maps a binary function over two lists at once. This is in the
Prelude and is called zipWith.
> fib'' :: [Int]
> fib'' = 1 : 1 : zipWith (+) fib (tail fib)
For more examples using infinite structures look in the demo files
that come with Yale Haskell. Both the pascal program and the
primes program use infinite lists.
Section: 3.5 The Error Function
Too late - we already used it!
Continued in part10.lhs