Skip to content

Instantly share code, notes, and snippets.

@kanak
Created March 26, 2011 00:19
Show Gist options
  • Save kanak/887886 to your computer and use it in GitHub Desktop.
Save kanak/887886 to your computer and use it in GitHub Desktop.
Bird's Introduction to Functional Programming: Chapter 02 Simple Datatypes Notes and Solutions
Richard Bird - Introduction to Functional Programming using Haskell [2ed]
Chapter 02: Simple Datatypes
> module Bird02 where
> import Data.Char (chr, ord)
================================================================================
2.1 Booleans
> data Bool' = False' | True'
> deriving (Show)
Define functions using pattern matching
> not' :: Bool' -> Bool'
> not' True' = False'
> not' False' = True'
How is not e simplified?
1. reduce e to normal form
2. if reduction terminates and results in True', then first equation is used.
otherwise second equation is used.
3. If e cannot be reduced, value of not e is undefined.
So, not bottom = bottom, "not" is a strict function.
So, there are THREE boolean values: True', False', Bottom
every datatype introduces an extra anonymous value: bottom
> and', or' :: Bool' -> Bool' -> Bool'
> False' `and'` _x = False'
> True' `and'` x = x
> True' `or'` _x = True'
> False' `or'` x = x
How is e1 || e2 simplified?
1. reduce e1 to normal form. If it fails, then value is undefined
2. if reduction succeeds and e1 is True, then True is returned
e1 is False, then x is evaluated and returned.
So, bottom || _ = bottom
True || bottom = True
False || bottom = bottom
So, || is strict in its left argument but not in its right.
If we had instead pattern matched against both arguments:
True || True = True
False || True = True
True || False = True
False || False = False
Then, the resulting function would have been strict in both arguments
========================================
2.1.1 Equality and comparison operators
> equal', nequal' :: Bool' -> Bool' -> Bool'
> equal' x y = (x `and'` y) `or'` (not' x `and'` (not' y))
> nequal' x y = not' (x `equal'` y)
"==" or equal' is a computable test for equality
"=" is used for definitions, and is mathematical equality.
So, bottom = bottom is true (everything equals itself), but bottom == bottom is bottom
since we can't compute it.
----------------------------------------
Equality tests (==), (/=) are defined in a type class:
class Eq a where
(==), (/=) :: a -> a -> Bool
To declare that something is in a type class we write:
instance Eq Bool where
x == y = (x && y) || (not x && not y)
----------------------------------------
Ord is a subclass of Eq:
class (Eq a) => Ord a where
(<), (<=), (>=), (>) :: a -> a -> Bool
only < is required because <= is just < or = , the others are negations.
----------------------------------------
Examples
> leapyear :: Int -> Bool
> leapyear y = y `mod` 4 == 0 && y `mod` 100 /= 0 || y `mod` 400 == 0
Function that determines the type of triangle
> data Triangle = Failure | Isosceles | Equilateral | Scalene
> deriving (Show)
Assumption: x <= y <= z
> analyse :: Int -> Int -> Int -> Triangle
> analyse x y z
> | x + y <= z = Failure
> | x == z = Equilateral
> | (x == y) || (y == z) = Isosceles
> | otherwise = Scalene
--------------------------------------------------------------------------------
Exercise 2.1.1: define "and" and "or" using conditional expressions
> andCond, orCond :: Bool -> Bool -> Bool
> andCond x y = if x then y else False
> orCond x y = if x then True else y
--------------------------------------------------------------------------------
Exercise 2.1.2: we want False < True (because we declared it in that order)
and False < False to be False, True < True to be False
why is this definition: (x < y) = (not x) or y incorrect?
answer: False < False would be true
--------------------------------------------------------------------------------
Exercise 2.1.3: Implication
> (==>) :: Bool -> Bool -> Bool
> True ==> False = False
> _ ==> _ = True
--------------------------------------------------------------------------------
Exercise 2.1.4: Rewrite the declaration of type class Eq by giving default definition of /=
> class Eq' a where
> (===), (/===) :: a -> a -> Bool
> x /=== y = not (x === y)
--------------------------------------------------------------------------------
Exercise 2.1.5 Rewrite analyse so that it doesn't assume x <= y <= z
Exercise 2.1.6 Write a function sort3 that sorts three integers into nondecreasing order.
> sort3 :: (Ord a) => a -> a -> a -> (a,a,a)
> sort3 x y z
> | x <= y = if y <= z then (x,y,z) else (x,z,y)
> | otherwise = if x <= z then (y, x, z) else (y, z, x)
> analyse' :: Int -> Int -> Int -> Triangle
> analyse' x y z = analyse a b c
> where (a,b,c) = sort3 x y z
--------------------------------------------------------------------------------
Exercise 2.1.7: How many equations do you have to write to make Triangle an instance of Ord?
In general, we need to have answers for each possible pairs. There are n choose 2 = 4C2 = 6 equations
--------------------------------------------------------------------------------
Exercise 2.1.8: Are there numbers that can be compared by (==) but cannot be sensibly compared with <?
Complex numbers do not form an ordered field (http://en.wikipedia.org/wiki/Inequality_(mathematics)#Complex_numbers_and_inequalities)
--------------------------------------------------------------------------------
Exercise 2.1.9: == Should have the following properties:
1. Reflexive: x == x for all x
2. Transitive: x == y and y == z means x == z
3. Symmetric: x == y means y == x
Show that these hold for == on bool
Recall: our definition of x equal y was (x `and` y) `or` (not x `and` (not y))
1. Reflexivity
x == x
= (x and x) or (not x and not x)
= x or (not x) [ a and a == a]
= true since this is a tautology
So reflexivity holds
2. Transitivity
x == y and y == z
= ((x and y) or (not x and not y)) and ((y and z) or (not y and not z))
Case: y is True
= ((x and True) or (not x and not True)) and ((True and z) or (not True and not z))
= (x or (not x and False)) and (z or (not z and False))
= (x or (not x)) and (z or (not z))
Since a or not a is a tautology:
= True and True = True
So transitivity holds in this case.
Case: y is False
= ((x and False) or (not x and not False)) and ((False and z) or (not False and not z))
= (x or (not x and True)) and (z or (not z and True))
= (x or (not x)) and (z or (not z))
= True and True (since a or not a is a tautology)
So transitivity holds here too.
Thus, transitivity holds in all cases.
3. Symmetry:
x == y
= (x and y) or (not x and not y)
= (y and x) or (not y and not x) by the transitivity of and
= y == x by definition
So, symmetry holds as well.
--------------------------------------------------------------------------------
Exercise 2.1.10 What properties of < would you like to hold?
Standard mathematical rules:
1. Transitivity: x < y and y < z means x < z
2. Irreflexivity: x < x is False for all x
3. Asymmetric: x < y means y !< x
================================================================================
2.2 Characters
Two primitives for working with chars:
ord :: Char -> Int
chr :: Int -> Char
Now we can define equality of chars (assuming we have equality of ints) by:
x == y = ord x == ord y
Same with Ord Char
We can also write:
> isDigit, isLower, isUpper :: Char -> Bool
> isDigit c = ('0' <= c) && (c <= '9')
> isLower c = ('a' <= c) && (c <= 'z')
> isUpper c = ('A' <= c) && (c <= 'Z')
> capitalize :: Char -> Char
> capitalize c
> | isLower c = chr $ ord c + shift
> | otherwise = c
> where shift = ord 'A' - ord 'a'
--------------------------------------------------------------------------------
Exercise 2.2.1 Write nextlet that returns the letter coming immediately after it
> nextlet :: Char -> Char
> nextlet 'z' = 'a'
> nextlet 'Z' = 'A'
> nextlet c = chr $ ord c + 1
Or we could use modulo arithmetic
--------------------------------------------------------------------------------
Exercise 2.2.2: digitval converts a digit to its numeric value
> digitVal :: Char -> Int
> digitVal d
> | isDigit d = ord d - ord '0'
> | otherwise = error "Not a digit!"
================================================================================
2.3 Enumerations
> data Day = Sun | Mon | Tue | Wed | Thu | Fri | Sat
> deriving (Show)
(recall that it also contains bottom as a value)
Writing Eq or Ord manually would require 7C2 = 21 equations each!
Instead, we encode it as integers and use integers for equality and ord instead.
This is basically the typeclass enum
> instance Enum Day where
> fromEnum Sun = 0
> fromEnum Mon = 1
> fromEnum Tue = 2
> fromEnum Wed = 3
> fromEnum Thu = 4
> fromEnum Fri = 5
> fromEnum Sat = 6
> toEnum 0 = Sun
> toEnum 1 = Mon
> toEnum 2 = Tue
> toEnum 3 = Wed
> toEnum 4 = Thu
> toEnum 5 = Fri
> toEnum 6 = Sat
Note: we must have fromEnum (toEnum x) = x, but this constraint cannot be expressed
in Haskell
With the nums we can now write:
> instance Eq Day where
> x == y = (fromEnum x) == (fromEnum y)
> instance Ord Day where
> x < y = (fromEnum x) < (fromEnum y)
> workday :: Day -> Bool
> workday x = Mon <= x && x <= Fri
> restday :: Day -> Bool
> restday = not . workday
> dayAfter :: Day -> Day
> dayAfter d = toEnum $ (fromEnum d + 1) `mod` 7
========================================
2.3.1 Automatic Instance Declarations
use the deriving clause:
data Day = Sun | Mon | Tue | Wed | Thu | Fri | Sat
deriving (Eq, Ord, Enum, Show)
--------------------------------------------------------------------------------
Exercise 2.3.1 : dayBefore
> dayBefore :: Day -> Day
> dayBefore d = toEnum $ (fromEnum d - 1) `mod` 7
--------------------------------------------------------------------------------
Exercise 2.3.2 : Direction which has north south east west and write reverse
> data Direction = North | South | East | West
> deriving (Enum, Show)
> reverseDirection :: Direction -> Direction
> reverseDirection North = South
> reverseDirection South = North
> reverseDirection East = West
> reverseDirection West = East
-OR-
> reverseDir :: Direction -> Direction
> reverseDir x
> | even a = toEnum $ a + 1
> | odd a = toEnum $ a - 1
> where a = fromEnum x
--------------------------------------------------------------------------------
Exercise 2.3.3: Declare bool as a member of type class enum
> instance Enum Bool' where
> fromEnum False' = 0
> fromEnum True' = 1
> toEnum 0 = False'
> toEnum 1 = True'
================================================================================
2.4 Tuples
(a,b) is like we had written data Pair = MakePair a b. except that it has syntactic support.
Note: bottom is of type (a,b).
Note also that (bottom, bottom) is distinct from just bottom
example:
> testPair :: (a, b) -> Bool
> testPair (x,y) = True
Here:
> test1 = testPair undefined
> test2 = testPair (undefined, undefined)
*Bird02> test1
*** Exception: Prelude.undefined
*Bird02> test2
True
"More generally, if alpha is an enum with m declared constructors,
beta is an enum with n declared constructors
then, (alpha, beta) has 1 + (m + 1) * (n + 1) values in total."
m + 1 because we have the m values and bottom
n + 1 because we have the n values and bottom
add 1 to the product because bottom is itself a of type (alpha, beta)
> pair :: (a -> b, a -> c) -> a -> (b, c)
> pair (f, g) x = (f x, g x)
> cross :: (a -> b, c -> d) -> (a, c) -> (b, d)
> cross (f, g) = pair (f . fst, g . snd)
Cross is from category theory.
Theorem: cross (f, g) bottom = (f bottom, g bottom)
Proof: cross (f, g) bottom
= pair (f . fst, g . snd) bottom [ defn of cross ]
= ((f . fst) bottom, (g . snd) bottom)
= (f (fst bottom), g (snd bottom))
= (f bottom, g bottom) [ fst and snd are both strict]
Compare with:
> cross' (f, g) (x, y) = (f x, g y)
bottom fails to pattern match with (x, y) so cross' (f, g) bottom = bottom
So, cross is lazier than cross' and can deliver additional results
Some more theorems
1. fst . pair (f, g) = f
2. snd . pair (f, g) = g
3. pair (f, g) . h = pair (f . h, g . h)
4. cross (f, g) . pair (h, k) = pair (f . h, g . k)
Proof of 1
(fst . pair (f, g)) x
= fst (pair (f, g) x)
= fst (f x, g x)
= f x
So, fst . pair (f, g) = f
Proof of 2
(snd . pair (f, g)) x
= snd (pair (f, g) x)
= snd (f x, g x)
= g x
So snd . pair (f, g) = g
Proof of 3
(pair (f, g) . h) x
= pair (f, g) (h x)
= (f (h x), g (h x))
= ((f . h) x, (g . h) x)
= pair (f . h, g . h) x
So, #3 holds
Proof of 4
cross (f, g) . pair (h, k)
= pair (f . fst, g . snd) . pair (h, k) [ defn of . ]
= pair (f . fst . pair (h, k), g . snd . pair (h, k)) [ by #3]
= pair (f . h, g . k) [ by #1 and #2 ]
Note: This proof was conducted entirely in functions. More of this in Chapter 12
========================================
Nullary Tuple: ()
The type () has two members: bottom and ()
Use it to convert constants into functions
> pifun :: () -> Float
> pifun () = 3.14159
now we can write square . square . pifun
instead of (square . square) pi
--------------------------------------------------------------------------------
Exercise 2.4.1 Prove that cross (f, g) . cross (h, k) = cross (f. h, g . k)
cross (f, g) . cross (h, k)
= cross (f, g) . pair (h . fst, k . snd) [ by defn of cross ]
= pair (f . h . fst, g . k . snd) [ since cross (f, g) . pair (h, k) = pair (f . h, g . k)]
= pair ((f . h) . fst, (g . k) . snd) [ associativity of . ]
= cross (f . h, g . k) [ by defn of cross ]
--------------------------------------------------------------------------------
Exercise 2.4.2 datatype declaration for triple
> data Triple a b c = MakeTriple a b c
> deriving (Show)
--------------------------------------------------------------------------------
Exercise 2.4.3
First triple has d,m,y of current date
Second has d,m,y of birthdate
Return age as a whole number of years
> birthday :: (Integer, Integer, Integer) -> (Integer, Integer, Integer) -> Integer
> birthday (d,m,y) (dd, mm, yy) = y - yy + shift
> where shift = if m > mm || m == mm && d >= dd then 0 else -1
Shift is just calculating if we're on or past the birthdate
--------------------------------------------------------------------------------
Exercise 2.4.4 Can we have (a,b) as a member of Enum if both a and b are Enum?
Yes. Since a and b are enum, both a and b are countably infinite.
So, we can have (a,b) be an enum by performing the mapping in the same way we
map rationals to the naturals.
================================================================================
2.5 Either
Example of a type whose value depends on other types:
Either a b = Left a | Right b
Either gives us a new type that has "combined" a and b
> case' :: (a -> c, b -> c) -> Either a b -> c
> case' (f, _g) (Left x) = f x
> case' (_f, g) (Right y) = g y
Using case', we can define plus (category theory term)
> plus :: (a -> c, b -> d) -> Either a b -> Either c d
> plus (f, g) = case' (Left . f, Right . g)
algebraic properties of case and plus:
1. case (f, g) . Left = f
2. case (f, g) . Right = g
3. h . case (f, g) = case (h . f, h . g)
4. case (f, g) . plus (h, k) = case (f . h, g . k)
Proof of 1
case (f, g) . Left
= case (f, g) (Left x)
= f x [ by definition of case]
Proof of 2
case (f, g) . Right
= case (f, g) (Right y)
= g y [ by definition of case ]
Proof of 3: by cases
Case 1: input is Left x
LHS = h (case (f, g) Left x)
= h (f x)
RHS = case (h . f, h . g) Left x
= (h . f) x
Case 2: input is Right x
LHS = h (case (f, g) Right x)
= h (g x)
RHS = case (h . f, h . g) Right x
= (h . g) x
Proof of 4
case (f, g) . plus (h, k)
= case (f, g) . case (Left . h, Right . k) [by definition of case]
= case ( case (f, g) . Left . h, case (f, g) . Right . k) [by #3]
= case ( (case (f, g) . Left) . h, (case (f, g) . Right) . k) [ by associativity of compose ]
= case ( f . h, g . k) [ Since case (f, g) (Left x) = f x and case (f, g) (Right x) = g x ]
--------------------------------------------------------------------------------
Exercise 2.5.1: Define a function of source Either Bool Char that behaves differently on
the three arguments: Left bottom, Right bottom, and bottom
> ex251 :: Either Bool Char -> Char
> ex251 (Right x) = 'a'
> ex251 (Left x) = undefined
ex251 bottom = bottom because we try to evaluate it
ex251 (Right bottom) = 'a'
ex251 (Left bottom) = undefined
--------------------------------------------------------------------------------
Ex 2.5.2: Prove that case (f, g). plus (h, k) = case (f . h, g . k)
Proved earlier.
================================================================================
2.6 Type Synonyms
use type to give alternative names:
> type Roots = (Float, Float)
> type Angle = Float
--------------------------------------------------------------------------------
2.6.1 New Types
when you use type, you're just giving it a new name.
so, == is defined for roots because we have it defined for floats.
This is a problem because maybe we want equality of Angle to be mod 2 pi
we could say data Angle = MakeAngle Float
but there are two problems:
1. We need to keep wrapping and unwrapping things to work with them
2. Since MakeAngle bottom is also of type angle, we have an extra element
So, Angle is not isomorphic to Float
Solution: use newtype
newtype Angle = MakeAngle Float
1. newtype is strict so MakeAngle Bottom is not actually of type Angle
2. there is no boxing unboxing overhead at runtime
--------------------------------------------------------------------------------
Exercise 2.6.1
Want to treat two distances as equal if they are less than 10 miles apart.
> distanceEqual :: (Ord a, Num a) => a -> a -> Bool
> distanceEqual x y = x == y || abs (x - y) < 10
can we call this == if Distance is a type synonym? No because then it just
uses the == of the type it is a synonym for.
--------------------------------------------------------------------------------
Exercise 2.6.2: Show that Jane and Dick are different:
> data Jane = MkJane Int
> newtype Dick = MkDick Int
> nonstrict :: Either Jane Dick -> String
> nonstrict (Left (MkJane _)) = "Jane"
> nonstrict (Right (MkDick _)) = "Dick"
*Bird02> nonstrict (Right undefined)
"Dick"
*Bird02> nonstrict (Left undefined)
"*** Exception: Prelude.undefined
I'm showing the constructor pattern match case from
http://www.haskell.org/haskellwiki/Newtype
================================================================================
2.7 Strings
type String = [Char]
there's a Show class to display objects:
class Show a where
showsPrec :: Int -> a -> String -> String
(see 5.3 and 7.3 for more details on the arguments)
--------------------------------------------------------------------------------
2.7.1 Put in ascending order: McMillan, Macmillan, MacMillan
MacMillan, Macmillan, McMillan
why? Lexicographic order and
*Bird02> 'm' > 'M'
True
*Bird02> "MacMillan" > "McMillan"
False
*Bird02> "MacMillan" < "McMillan"
True
*Bird02> "Macmillan" < "McMillan"
True
--------------------------------------------------------------------------------
2.7.2 What are the values of:
show (show 42)
= show "42"
= "\"42\""
show 42 ++ show 42
= "42" ++ "42"
= "4242"
--------------------------------------------------------------------------------
2.7.3 showDate
e.g. showDate (10, 12, 1997) = "10 December, 1997"
> months = ["January", "February", "March", "April", "May", "June", "July", "August",
> "September", "October", "November", "December"]
> showDate (d, m, y) = showDay d ++ " " ++ showMonth m ++ " " ++ showYear y
> showDay :: Integer -> String
> showDay d = show d ++ suffix (d `rem` 10)
> where suffix 1 = "st"
> suffix 2 = "nd"
> suffix 3 = "rd"
> suffix _ = "th"
> showMonth m = months !! (m - 1)
> showYear y = show y
*Bird02> showDate (10, 12, 1997)
"10th December 1997"
================================================================================
2.8 Further Notes
Modern accounts of boolean algebra and logic:
Ben-Ari (1993): Mathematical Logic for Computer Science
Burke and Foxley (1996): Logic and its Applications
Gries and Schneider (1995): A logical approach to discrete Math
Theoretical Foundations of Type classes:
Wadler and Blott (1989): How to make ad-hoc polymorphism less ad-hoc
Jones (1992): Qualified Types: Theory and Practice
Jones (1995): A system of constructor classes: overloading and implicit higher-order polymorphism
Category Theory:
Barr and Wells (1995): Category Theory for Computer Science
Pierce (1991): Basic Category Theory for Computer Scientists
Bird and de Moor (1997): The Algebra of Programming
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment