Our Haskell topics this week were
recursive value definitions
records
instance declarations
kinds
You can read about records, instance declarations, and kinds in chapter 8 "Making Our Own Types and Typeclasses" of the Learn You a Haskell textbook.
In
our earliest weeks of studying Haskell we saw that a range of
integers (or another enumerable type) can be unbounded at one end,
which forms an infinite list. For example, [1
..]
is
an infinite list of all positive integers.
Let's look at how we can define our own infinite lists without using the range operator. One approach is to use a recursive function:
ints :: [Int]
ints = f 1 where f a = a : f (a + 1) > take 10 ints [1,2,3,4,5,6,7,8,9,10]
Notice that our recursive function f has no base case. In most languages, a recursive function with no base case will cause a program to hang, since it can never terminate. But due to Haskell's lazy evaluation, such a function causes no problem in Haskell.
Alternatively,
we may define the infinite list [1,
2, 3, ... ]
as
a recursively
defined value.
In other words, we may define the list in
terms of itself.
Suppose that ints
is
the list [1,
2, 3, ...]
.
Notice that if we add 1 to every element in ints, we get [2, 3,
...]
,
which is the tail of ints. In other words, we
have observed that
ints == 1 : [i + 1 | i <- ints ]
In Haskell, we may define ints using this equation! In other words, we can write
ints = 1 : [i + 1 | i <- ints ]
and now ints
is just the same as before:
> take 10 ints [1,2,3,4,5,6,7,8,9,10]
Even more compactly, we can define ints
as
ints = 1 : map (+1) ints
Let's look at another example. Recall the Fibonacci series:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
We'd like to define an infinite list of all Fibonacci numbers. As before, one way is to use a recursive function:
fibs = f 1 1 where f a b = a : f b (a + b)
It works:
> take 10 fibs [1,1,2,3,5,8,13,21,34,55]
Alternately, as before we can directly define the sequence recursively. Once more we must find a recursive pattern. Suppose that
fibs == [1, 1, 2, 3, 5, 8, 13, 21, ...]
Then
tail fibs == [1, 2, 3, 5, 8, 13, 21, 34, ...]
and
tail (tail fibs) == [2, 3, 5, 8, 13, 21, 34, 55, ...]
Notice above that if we add fibs
and
(tail fibs)
element by element, we get
tail
(tail fibs)
.
That makes sense, because for n >= 3 we have
Fn = Fn - 1 + Fn - 2
so we can generate each element as the sum of the elements that are one and two positions earlier.
We can use this pattern to define fibs
recursively. The zipWith
function
combines elements of two lists using an arbitrary function, so we can
use it to add lists element by element. For example:
> zipWith (+) [1, 2, 3] [100, 150, 200] [101,152,203]
And so we can define
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
The sequence is the same as before:
> take 10 fibs [1,1,2,3,5,8,13,21,34,55]
It may seem surprising that this is possible at all. But due to Haskell's lazy evaluation this works fine.
Here is the Frac type we saw in the lecture:
data Frac = Frac Int Int -- numerator, denominator instance Eq Frac where (==) (Frac a b) (Frac c d) = (a * d == b * c) instance Ord Frac where compare (Frac a b) (Frac c d) = compare (a * d) (b * c)
Here's a generalized version, in which the numerator and denominator may be of any integral type (i.e. either Int or Integer):
-- a is any Integral type data GFrac a = GFrac a a instance (Num a, Eq a) => Eq (GFrac a) where (==) (GFrac a b) (GFrac c d) = (a * d == b * c) instance (Num a, Ord a) => Ord (GFrac a) where compare (GFrac a b) (GFrac c d) = compare (a * d) (b * c)
Write the built-in function iterate
,
which produces an infinite list by repeated applying a function to a
value.
iterate :: (a -> a) -> a -> [a] iterate f x = x : iterate f (f x) -- x, f x, f f x, f f f x, ..... iterate' :: (a -> a) -> a -> [a] iterate' f x = let list = x : map f list in list
Construct an infinite diagonal matrix as an infinite list of lists.
infinite_diag :: [[Int]] infinite_diag = iterate' (0:) (1 : repeat 0)
Construct Pascal's triangle as an infinite list of lists.
-- transform one row of Pascal's triangle to the next nextRow :: [Int] -> [Int] nextRow list = [a + b | (a, b) <- zip (0 : list) (list ++ [0]) ] pascal :: [[Int]] pascal = iterate' nextRow [1]
Create a Haskell type Time that represents a time of day with 1-second resolution, such as 5:15:02 or 13:12:45.
data Time = Time { hours :: Int, minutes :: Int, seconds :: Int } deriving (Eq, Ord) instance Enum Time where toEnum i = Time (i `div` 3600 `mod` 24) (i `div` 60 `mod` 60) (i `mod` 60) fromEnum (Time h m s) = s + (m + h * 60) * 60 instance Bounded Time where minBound = Time 0 0 0 maxBound = Time 23 59 59 instance Show Time where show (Time h m s) = show h ++ ":" ++ show m ++ ":" ++ show s makeTime :: Int -> Int -> Int -> Time makeTime = Time