View
13
Download
0
Category
Preview:
Citation preview
1
Haskell Programming Fundamentals: Learn Pure Functional Thinking
Instructor: Barry Burd, Barry@Burd.org
Copyright © 2019, Barry Burd
What to install:
Haskell Platform, https://www.haskell.org/platform/
Haskell for Everyone IDE, https://haskellforeveryone.com
Haskell Expressions
5 + 4 -- ghci shows you the value of the expression
(+) 5 4 -- parentheses turn infix to prefix
(+) (5 4) -- error message
addFive = (+) 5 -- partial evaluation
addFive 4
div 5 2
5 `div` 2 -- back ticks turn prefix to infix
doubleme x = x + x
doubleme 5
:t 5 -- a ghci command (not a Haskell statement)
:t (+) -- :t is short for :type
Haskell’s Predefined Types (https://www.haskell.org/onlinereport/haskell2010/haskellch6.html)
2
More Haskell Expressions
add = (+)
mult = (*)
add 5 18
mult 5 18
add (5 18)
(add 5) 18
(add 5) 18
x = 21
y = 13
z = add x
z y
succ 6
succ succ 6
(succ succ) 6
succ (succ 6)
3 + 5 * 10
succ (succ (succ 6))
succ $ succ 6
succ $ succ $ succ 6
Haskell's precedence and associativity table
(https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-820004.4.2)
3
More Haskell Expressions
a 3 == 5 3 /= 5 -- not equal
(True && True) || (5 == 7)
not True
add (mult 5 4) $ mult 7 9 -- $ has the lowest precedence
(add (mult 5 4)) $ mult 7 9 -- $ is like enclosing here to the end
-- of the line in parentheses
"Hello"
:info succ
"Hello" ++ ", there!" -- list concatenation
[5,7,8,12,13]
[5, 7, "Hello"] -- error message
[1..10]
[] -- Behold! The empty list
[1,4,6] ++ [9,8,3]
['H', 'e', 'l', 'l', 'o']
mylist = [33, 11, 32, 1, 7]
head mylist -- Don't forget:
-- head is a list entry but
tail mylist -- tail is a list of entries
(head mylist) ++ (tail mylist)
[head mylist] ++ (tail mylist)
(head mylist):(tail mylist) -- the colon operator
head $ tail mylist
init mylist -- init and last
last mylist
x = init mylist
y = last mylist
newstuff = x ++ [y]
newstuff
newstuff == mylist
firstplusone x = head x + 1
firstplusone mylist
firstplustoneV2 = succ . head -- the composition operator
firstplustoneV2 mylist
firstplusoneV3 x = succ head x
firstplusoneV3 x = succ $ head x
firstplusoneV3 mylist
(succ . head) mylist
succ . head mylist
succ . head $ mylist
last . init $ mylist
nexttolast = last . init
nexttolast mylist
listoflists = [[1,2], [5,6,7], [8,9,10]]
head listoflists
4
putStrLn "Hello"
putStrLn 3
putStrLn "3"
show 3
putStrLn (show 3) -- don't forget show
putStrLn $ show 3
print 3
Exercise Set 1
Solutions are in the back of this manual.
1. Given a list named b, write a line of code to find the number that's one more than the first number in
the list. For example, if b is [5, 19, 21, 6], write a line of code that outputs 6.
2. Define a new function (named f2) that does what you did in Question 1.
3. Write a line of code to find the sum of the first two numbers in a list. For example, if b is [5, 19, 21, 6],
the output of this line of code is 24.
4. Define a new function (named f4) that does what you did in Question 3.
5. Write a line of code to create a list consisting of the first two numbers in a list. For example, if b is
[5, 19, 21, 6], the output of this line of code is [5, 19].
6. Define a new function (named f6) that does what you did in Question 5.
7. Write a line of code to create a list consisting of all but the first two numbers in a list. For example, if
b is [5, 19, 21, 6], the output of this line of code is [21, 6].
8. Explain why tail tail b is not a valid solution for Question 7.
9. If b is [5, 19, 21, 6], what's wrong with the following expression? tail (head b)
10. Write a point-free definition of the function that you defined in Question 7. (Remember: A point-free
definition has no variables in it.)
11. You're trying to create a point-free definition of the function that you defined in Question 2. What's
wrong with the following definition? f2 = head + 1
12. (More difficult) Write a point-free definition of the function that you defined in Question 2.
5
More Haskell Expressions
mylist = [5, 19, 21, 6, 9, 15, 0]
f x = tail $ tail x
f mylist
g = tail . tail
g mylist
:t True
:t 5
:t 5.0
:t "Hello"
:t 'a'
:t (+)
:t tail
:t head
:t g
:t 4
:t (show 4)
read "38" :: Int -- opposite of show
read "38" -- error
read ("38" :: Int) -- error
[4,5,6]
[4,5, "x"]
mylist
head mylist
tail mylist
5: $ tail mylist
5:(tail mylist)
take 5 [1..] -- lazy evaluation
[1.0, 1.25..2.0]
take 10 [1..]
[1..] -- has to be interrupted
(8, "Hello", True)
mytuple = (8, "Hello", True) -- tuples are heterogeneous
fst mytuple
mytuple = (8, "Hello")
fst mytuple -- fst and snd for 2-tuples only
snd mytuple
x = 10
inRange y = if y < 5 then "small" else "big" –- if..then..else
-- is an expression
inRange x
inRange y = if y < 5
:set +m
inRange y = if y < 5
then "small"
else "big"
inRange 16
6
z <- getLine
z
inRange z
inRange (read z)
inRange $ read z
mysentence <- getLine
mysentence
read mysentence
-- This is a comment
status x = if head x < last x
then "last is bigger"
else (if head x > last x
then "head is bigger"
else "equal")
status [3,4,5]
status [5,4,3]
status [5,3,2,1,5]
status x = if head x < last x then "last is bigger" else if head x >
last x then "head is bigger" else "equal"
status [3,4,5]
status [4,3,1]
status [1,1]
fst (2,5)
snd (2,5)
fst (2, "Hello")
mytupple = (1, 4,2,7)
mytupple
thd (a, b, c, d) = c
thd mytupple
thd (1,2,3,4,5)
thd (_, _, c, _) = c -- underscore wildcard
thd (a, a, c, a) = c
thd (_, _, c, _) = c
thd mytupple
take 5 [2,4,6,3,4,7,9,4,0]
mylist = [2,4,6,3,4,7,9,4,0]
take 5 mylist
take 5 [1..]
drop 5 mylist -- drop
take 5 mylist ++ drop 5 mylist
(take 5 mylist ++ drop 5 mylist) == mylist
10:mylist
head mylist
tail myList
tail mylist
splitAt n list = (take n list, drop n list)
splitAt 3 [1,6,9,8,1,2,5]
makeList a = [a]
7
makeList 7
mylist
add1 = (+)
add1 x = x + 1
add1 6
map add1 mylist -- map
isEven n = n % 2 == 0
isEven n = (n % 2 == 0)
isEven n = (n mod 2 == 0)
isEven 5
isEven n = ((n mod 2) == 0)
isEven 5
isEven n = (n `mod` 2 == 0)
isEven 5
areEven list = map isEven list
areEven mylist
mylist
filter isEven mylist -- filter
isSmall x == x < 5
isSmall x == (x < 5)
isSmall x = (x < 5)
isSmall x = x < 5
map isSmall mylist
filter isSmall mylist
mylist
:info foldl
foldl (+) 0 mylist
mylist
4,7,9,4,0]
foldl (+) 10 mylist -- foldl and foldr
foldl (*) 1 mylist
mylist = [3,5,7,9, 2,10,1]
mylist
linear x = 3*x + 7
map linear mylist
map (\x -> 3*x + 7) mylist -- lambda expression
map (\x -> 10*x - 4*x**x) mylist
(\x y -> x + 2*y) 4 8
emp1 = ("Joe", 10000.00)
emp2 = ("Jane", 30000.00)
emp3 = ("Mary", 5000.00)
emp4 = ("Ed", 25000.00)
emps = [emp1, emp2, emp3, emp4]
500 + 10000.00
500.00 + 10000.00
name emp = fst emp
name emp1
salary emp = snd emp
8
salary emp2
map name emps
map salary emps
map (\emp -> salary emp + 500.00) emps
tenth (_, _, _, _, _, _, _, _, _, a) = a
tenth (1,2,3,4,5,6,7,8,9,10)
tenth (1,2,3,4,5,6,7,8,9,54)
map (\emp -> "**" ++ name emp ++ "**") e
foldl (+) 0 $ map salary emps
foldl (++) "" $ map name emps
foldl (\x y -> x ++ " " ++ y) "" $ map name emps
foldl (\x y -> x ++ " " ++ y ++ " ") "" $ map name emps
numbers = [1,2,5]
foldl (+) 0 numbers
foldr (+) 0 numbers
foldl (-) 0 numbers -- subtraction isn't associative
foldr (-) 0 numbers
Exercise Set 2
Here are some employees: emp1 = ("Joe", 10000.00)
emp2 = ("Jane", 30000.00)
emp3 = ("Mary", 5000.00)
emp4 = ("Ed", 25000.00)
emps = [emp1, emp2, emp3, emp4]
1. How can you get an employee's name and salary?
2. How can you get all names; get all salaries?
Here are examples of the use of lambda expressions: (\x -> x * 2 + 3) 7
map (\x -> x + 5) [3,6,2]
3. Use a lambda expression to make a list of (salary, name) tuples.
4. Find the sum of all salaries.
5. Find the salaries less than or equal to 20000.00.
6. Add 500.00 to any salary that's less than or equal 20000.00.
7. Find the sum of salaries that are increased by the previous question.
8. Find the sum of the increases from the previous two questions.
9. Find the sum of all new salaries.
9
More Haskell Expressions
500.0 * 2
500.0 * length [2,3]
:t 2
500.00 * fromIntegral (length [2,3]) -- need type conversion
:t 500.00
5 / 2
div 5 2
5 `div` 2
5.0 / length [3,4]
5.0 / fromIntegral (length [3,4])
5.0 / fromInteger (length [3,4])
fib 0 = 0 ; fib 1 = 1 ; fib n = fib (n-1) + fib (n-2) -- recursion
:t fib
fib 5
fib 6
fib 0
nth 0 list = head list ; nth n list = nth (n-1) $ tail list
nth 5 [3,2,6,4,7,8,0]
nth 0 (x:xs) = x ; nth n (x:xs) = nth (n-1) xs
nth 5 [3,2,6,4,7,8,0]
nth 0 (x:_) = x ; nth n (_:xs) = nth (n-1) xs
nth 5 [3,2,6,4,7,8,0]
contains k [] = False ; contains k (x:xs) = k == x || contains k xs
contains 8 [1,2,4,6,3]
contains 8 [1,2,4,8,6,3]
rev [] = [] ; rev [a] = [a] ; rev (x:xs) = rev xs ++ [x]
rev "Barry"
rev [1,7,3,5,8,2]
rev2 [] = [] ; rev2 (x:xs) = rev xs ++ [x]
rev2 [2,4,5]
rev2 [3]
Exercise Set 3
1. Zip the elements of two lists of equal length together.
2. Produce a list with n identical elements.
3. Merge two sorted lists a la MergeSort.
4. Flip every two elements (every two adjacent ones).
5. Check if a list is sorted (in increasing order).
6. Find the smallest element in a list.
7. Remove the smallest element from a list (using the previous function).
8. Do a Selection Sort (using the previous two functions).
10
Creating types from existing types:
type ISBN = Int -- type synonyms
type Title = String
type Author = String
type Authors = [Author]
data Book =
Book {isbn :: ISBN, title :: Title, authors :: Authors} deriving (Show)
-- data type declaration
title2 (Book _ t _) = t
algOfProgramming =
Book 9780135072455 "Algebra of Programming" ["Richard Bird", "Oege de Moor"]
javaForDummies = Book 1118407806 "Java For Dummies" ["Barry Burd"]
type Price = Double
type BookRecord = (Book, Price)
javaRecord = (javaForDummies, 29.95)
Here are some expressions that use the definitions:
algOfProgramming
data Student = Student String Double
student1 = Student "Joe" 2.00 -- creating a value of a data type
student2 = Student "Bukowski" 0.00
data Sale = Purchase String Double
:type (+)
:t (+)
:t Student
:t Purchase
student1
data Student = Student String Double deriving (Show)
student1 = Student "Joe" 2.00
student2 = Student "Bukowski" 0.00
student1
name Student n _ = n
name (Student n _) = n
name student2
isbn algOfProgramming
:t isbn
title algOfProgramming
:t title
: Book -> Title
title2 algOfProgramming
head $ authors algOfProgramming
11
head "Hello"
head $ head [[2,3], [5,7]]
head $ head ["Hello", "Goodbye", "Go away"]
rec1 = (javaForDummies, 4000.00)
:t rec1
head (authors (fst rec1))
head $ authors $ fst rec1
rec1
Exercise set 4
A Block has three Double values (length, width, and height).
Note: In Haskell, length is the name of a function that returns the
number of elements in a list, so it's safest for you not to try to
reuse that name.
1. Create a list consisting of a few Blocks.
2. Create a function that takes a list of Blocks and returns a list consisting
of the volumes of the Blocks.
3. Create a function that takes a list of Blocks and returns the average of the
volumes of the Blocks.
Note: To divide a Double value by an Int value, do something like this:
theDoubleValue / (fromIntegral theIntValue)
4. Create a function that takes a list of Blocks and returns a list consisting
of similarly shaped Blocks, each having volume 1.
Notes:
To take a number to a power, use the ** symbol. For example, x**(1/3) stands for
the cube root of x (approximately).
Let's say you have a Block whose sides have sizes 3, 4, 5. Then the cube root of the
volume is (3 * 4 * 5)**(1/3) and a similarly shaped Block with volume 1 has sides
3/((3 * 4 * 5)**(1/3)) , 4/((3 * 4 * 5)**(1/3)) , 5/((3 * 4 * 5)**(1/3)).
Exercise Set 5
Create code that makes an Item type.
An Item value has a name (such as "Shirt") and a price (such as 20.00).
Create code that makes a Customer type.
A Customer has a name and a creditCardNumber.
Create a Purchase type. A Purchase has a Customer and a list of items.
In each of the following exercises, you can use functions that you defined in earlier exercises. For
example, for Exercise 5, you can use any of the functions that you defined in Exercises 1, 2, 3 and 4.
12
0. Create a list of two purchases, named purchasesForToday and show the total of all purchases.
1. Recall that a Purchase contains one or more Items, and each Item has a price. Create a function
that takes a purchase and finds the total amount of that purchase.
2. From your purchasesForToday list, create a list of purchases valued at more than 100.00.
3. From your purchasesForToday list, create a list of customers who have purchased more than
100.00 in goods.
4. Create a list of the names of customers who have purchased more than 100.00 in goods.
5. Create a list of strings of the following kind: "Dear Mary, You've spent more than $100.00."
6. Create a list of strings of the following kind: "Dear Mary, You've purchased $125.2314
in goods." Only customers with more than $100.00 in purchases get this message. Remember that
you can't concatenate a number (such as 125.2314) with a String unless you convert the number
into a String. You do this with the show function, as follows: show 125.2314 ++ " is a lot of money."
7. Modify the function that you created in Exercise 6. But this time, display numbers with only two digits
to the right of the decimal point. For example, "Dear Mary, You've purchased $125.23 in
goods." Here's how you can display a number with only two digits to the right of the decimal point:
Add the following line to the beginning of your program:
import Text.Printf (printf)
Then you can do the following:
x = 3.1415926535
(printf "%.2f" x) :: String
"3.14"
Note that an expression such as
(printf "%.2f" x) :: String
evaluates to a String. So you can do something like this:
"The value of PI is " ++ (printf "%.2f" x) :: String
"The value of PI is 3.14"
13
I/O (and guards):
import System.IO
fib n = if n == 1 || n == 2
then 1
else fib (n - 1) + fib (n - 2)
-- Here comes a guard:
fibList n
| n == 1 = [1]
| n == 2 = [1, 1]
| otherwise = let prevList = fibList(n - 1)
newElement = last prevList + last (init prevList)
in prevList ++ [newElement]
-- Some simple I/O:
main = do
putStr "Enter a number: "
hFlush stdout
x <- getLine
print $ fibList (read x)
Exercise Set 6:
Create a function that returns True if a list has two adjacent identical elements. Otherwise, the
function returns False. For example, when it acts on [1,2,2] the function returns True; when it
acts on [2,1,2] the function returns False. And when it acts on [2], the function returns False.
14
Algebraic types:
data Color = Red | Orange | Yellow | Green | Blue | Indigo | Violet
deriving (Eq, Ord, Show, Bounded, Enum) -- use existing type classes
lastColor = maxBound :: Color -- double colon is required
firstColor = minBound :: Color
colorsStartingWith color =
color:(if ((succ color) /= lastColor)
then colorsStartingWith (succ color)
else [succ color])
colorsToList = colorsStartingWith firstColor
reverseColors = reverse colorsToList
colorsEndingWith color =
color:(if ((pred color) /= firstColor)
then colorsEndingWith (pred color)
else [pred color])
colorsToReverseList = colorsEndingWith lastColor
colorOrder = [firstColor .. lastColor]
reverseColorOrder = [lastColor, pred lastColor .. firstColor]
intAverage n1 n2 = ceiling (((fromIntegral n1) + (fromIntegral n2)) / 2 )
paintMix c1 c2 = toEnum (intAverage (fromEnum c1) (fromEnum c2)) :: Color
main = do
putStrLn $ show (colorsStartingWith Red)
putStrLn $ show colorsToList
putStrLn $ show reverseColors
putStrLn $ show reverseColorOrder
putStrLn $ show colorsToReverseList
15
Types within types:
type CardHolder = String
type CardNumber = String
type Address = [String]
type CustomerID = Int
data BillingInfo =
CreditCard {cardNumber :: CardNumber,
cardHolder :: CardHolder,
address :: Address}
| CashOnDelivery
| Invoice {customerID :: CustomerID}
deriving (Show)
data Color =
Red | Orange | Yellow | Green | Blue | Indigo | Violet
deriving (Eq, Ord, Show, Bounded, Enum)
billInfo1 = CreditCard "1111222233334444" "Bob" ["111 Main Street", "NJ"]
billInfo2 = CashOnDelivery
billInfo3 = Invoice 12345
billInfo4 = CreditCard {address = ["111 Madison Avenue", "Madison"],
cardHolder = "Ellen", cardNumber = "1111333344445555"}
billInfo5 = CreditCard "5555444433332222" "Moe" []
billInfo6 = CreditCard "8888777766665555" "Larry"
["220 Harvey Lane", "San Franciso", "CA"]
billInfo7 = CreditCard "5555333377778888" "Fred" ["Parts Unknown"]
color1 = pred Green
color2 = succ Green
color3 = minBound :: Color
color4 = maxBound :: Color
value1 = toEnum 3 :: Color
value2 = fromEnum Yellow
-- case expression is for pattern matching
whatToDo billInfo =
case billInfo of
CreditCard num holder address -> "Process " ++ num ++ " for " ++ holder
CashOnDelivery -> "Get payment at customer's front door"
Invoice id -> "Send bill to customer " ++ show id
whatToDo2 (CreditCard num holder address) =
"Process " ++ num ++ " for " ++ holder
whatToDo3 (CreditCard num holder address)
| length address == 0 = "Process " ++ num ++ " for " ++ holder
| length address == 1 = "Process " ++ num ++ " for " ++ holder
++ " at " ++ head address
16
| otherwise = "Process " ++ num ++ " for " ++ holder
++ " at " ++ head address ++ ", "
++ last address
isBig x
| x > 100 = True
| otherwise = False
main = do
putStrLn $ "whatToDo billInfo1: " ++ whatToDo billInfo1
putStrLn $ "whatToDo3 billInfo5: " ++ whatToDo3 billInfo5
putStrLn $ "whatToDo3 billInfo6: " ++ whatToDo3 billInfo6
putStrLn $ "whatToDo3 billInfo7: " ++ whatToDo3 billInfo7
A parameterized type:
data Thing = A|B|C
instance Show Thing where
show A = "a"
show B = "b"
show C = "c"
data List a = Cons a (List a)
| Nil
deriving (Show)
list0 = Cons 0 Nil
list1 = Cons 1 list0
list2 = Cons 2 list1
data List' = Cons' Int List' | Nil'
list0' = Cons' 0 Nil'
list1' = Cons' 1 list0'
list2' = Cons' 2 list1'
showWithoutBrackets Nil' = ""
showWithoutBrackets (Cons' n list') =
show n ++ "," ++ showWithoutBrackets list'
instance Show List' where
show list' = "[" ++ init (showWithoutBrackets list') ++ "]"
main = do
putStrLn $ show list2
putStrLn $ show A
putStrLn $ show list2'
17
More I/O:
import System.IO
main = do
hSetBuffering stdout NoBuffering
putStr "Enter a number: "
string1 <- getLine
--let number1 = (read string1 :: Double)
let number1 = read string1
putStr "Enter another number: "
string2 <- getLine
--let number2 = (read string2 :: Double)
let number2 = read string2
putStrLn $ "The sum is " ++ show (number1 + number2)
Create a type class instance:
import Text.Printf
data Purchase = Purchase {name :: String, price :: Double}
instance Show Purchase where
show p = "You paid " ++ printf "%.2f" (price p) ++ " for the " ++ (name p)
instance Eq Purchase where
p1 == p2 = (name p1) == (name p2)
purchase1 :: Purchase
purchase1 = Purchase "TV" 300.00
oneDollarMore :: Purchase -> Purchase
oneDollarMore (Purchase n p) = Purchase n (p + 1)
Exercise Set 7:
1. Define a type named Units. The Units type has two values, Feet and Meters.
2. Define a type named Length. Each length has two values: a number (which is a Double) and a
number of units (which is of type Units).
3. Make your Length type an instance of the Show class.
4. Define a class named Convertible. If something is Convertible, it must have a toUS function.
Remember: When you define the Convertible class, you don't give a formula for the toUS function,
you only declare the existence of a toUS function for Convertible types. In this code, you'll declare
the toUS function's existence this way:
toUS :: a -> a
18
This means that, if you have a value of type a, and you apply the toUS function to that value, the
function will return a value of type a.
5. Declare your Length type to be an instance of the Convertible class. To do so, you must define
what toUS means for a Length value. (Of course, it depends on which Units the Length value has.)
6. Declare your Length type to be an instance of Haskell's Eq class. For example, Length 1 Feet
== Length 1 Feet, and Length 3.28 Feet == Length 1 Meters. Use the toUS
function to define equality for lengths.
7. Define a type named Locale. The Locale type has three values, US, France, and England.
8. Define a type named Currency. Each Currency value has two things: a Locale and a number
(which is a Double). For example, I might have Currency US 10.00 in my pocket.
9. Make Currency be an instance of Haskell's Show class.
10. Make Currency be an instance of your Convertible class. To do so, you must define what
toUS means for a Currency value. (Of course, it depends on which Locale the Currency value
has.)
11. Make Currency be an instance of Haskell's Eq class. For example, Currency 1 US =
Currency 1 US, and Currency 1 England == Currency 1.25 US. Use the toUS
function to define equality for currency values.
12. Define a type named Scale. The Scale type has two values: Fahrenheit and Celsius.
13. Define a type named Temperature. Each Temperature has a numeric value and a Scale.
14. Make your Temperature type an instance of Haskell's Show class.
15. Make your Temperature type an instance of your Convertible class (with Fahrenheit as
the US temperature).
16. Make your Temperature type an instance of Haskell's Eq class. Use the toUS function to define
equality for temperatures.
17. Define a type named HowManyPeople. Each HowManyPeople value has a number and a
Locale. For example,
numberVotingInUSin2016 = HowManyPeople 130000000 US
18. Here are population figures for the three locales:
population US = 322762018
population England = 55040000
population France = 64668129
Make your HowManyPeople type be an instance of the Convertible class. When you convert from
one HowManyPeople value to another, you keep the percentage of the country's population the same
19
(or nearly the same). For example, half the US population is 161381009, and half of France's
population is 32334064. So
toUS (HowManyPeople 32334064 France) is HowManyPeople 161381009 US
19. Make your HowManyPeople type an instance of Haskell's Eq class. Use the toUS function to
define equality for HowManyPeople values.
20
The Maybe monad:
sqrtMaybe x = if x >= 0
then Just (x**(0.5))
else Nothing
recipMaybe 0 = Nothing
recipMaybe x = Just (1 / x)
f x = sqrtMaybe x >>= recipMaybe
fNew x = return x >>= sqrtMaybe >>= recipMaybe
p x = if x == Nothing then "Error" else "Not error"
f' x = do
y <- sqrtMaybe x
recipMaybe y
f''' x = (sqrtMaybe >> recipMaybe) x :: Maybe Double
f'''' x = do
sqrtMaybe x
recipMaybe x
main = do
putStrLn "\n"
putStrLn $ show $ f 4
putStrLn $ show $ f 0
putStrLn $ show $ f (-1)
putStrLn ""
putStrLn $ show $ fNew 4
putStrLn $ show $ fNew 0
putStrLn $ show $ fNew (-1)
putStrLn ""
putStrLn $ show $ f' 4
putStrLn $ show $ f' 0
putStrLn $ show $ f' (-1)
putStrLn ""
putStrLn $ show $ f''' 4
putStrLn $ show $ f''' 0
putStrLn $ show $ f''' (-1)
putStrLn ""
putStrLn $ show $ f'''' 4
putStrLn $ show $ f'''' 0
putStrLn $ show $ f'''' (-1)
putStrLn ""
21
Define a type class instance:
type Name = String
type Rating = Double
data Hotel = Hotel {name :: Name, rating :: Rating} deriving (Show)
data OverallQuality = Good | Marginal | Bad deriving Show
class Evaluate a where
evaluate :: a -> OverallQuality
instance Evaluate Hotel where
evaluate x = if rating x > 3.0 then Good
else if rating x < 3.0 then Bad
else Marginal
ritz = Hotel "Ritz" 5.0
dump = Hotel "Dump" 1.0
ok = Hotel "OK" 3.0
main = do
print $ evaluate ritz
print $ evaluate dump
print $ evaluate ok
Exercise Set 8:
You arrive at a restaurant at 6pm, but you don't get seated until 6:25pm. You might say that your arrivalTime is 600 but your seatedTime is 625. In this case, your waiting time is 25.
1. Write a function named waitingTime. The function takes two Int values
(arrivalTime and seatedTime) and returns the customer's waiting time. [Assume
that a time such as 6:00pm is represented by the Int value 600.)
2. If the input values arrivalTime and seatedTime are incorrect, it doesn't make
sense to calculate the waiting time. For example, one customer answers a survey by writing that they arrived at 5:30pm and were seated at 5:00pm. That's bad input. Write a function named maybeWaitingTime. The function takes two Int values (arrivalTime and seatedTime) and returns a Maybe Int value. The Int value is the customer's waiting time.
3. Write a function named maybeServiceWasOK. This function takes an Int value (the
waiting time) and returns a Maybe Bool value. The Bool value is True for a waiting
22
time of beteen 0 and 30 minutes, and False for a waiting time that's more than 30 minutes..
4. Create a function that binds the maybeWaitingTime and maybeServiceWasOK
functions together. The function's input is an arrivalTime and a seatedTime. The
function's output is a Maybe Bool value.
5. Create a function named maybeShowBoolean. The maybeShowBoolean function
takes a Bool value and returns a Maybe String value. When the Bool value is True, the String value is "Good Service". When the Bool value is False, the
String value is "Bad Service".
6. Create a function that binds the maybeWaitingTime, maybeServiceWasOK, and maybeShowBoolean functions together. The function's input is an arrivalTime
and a seatedTime. The function's output is a Maybe String value.
23
Another I/O example:
prompt :: String -> IO String
prompt x = do
putStrLn x
number <- getLine
return number
accum currentList = do
rawNum <- getLine
let num = read rawNum :: Int in
if num /= 0
then accum $ currentList ++ [num]
else print currentList
main :: IO ()
main = accum []
Yet another I/O example:
accum'' current f test myRead = do
string <- getLine
let num = myRead string in
if test num
then accum'' (f num current) f test myRead
else print current
numberOfEs str =
sum $ map (\char -> if char == 'e' || char == 'E' then 1 else 0) str
main = do
putStrLn "Enter several Int values, the last of which is 0 ..."
accum'' [] (\num current -> current ++ [num :: Int]) (/= 0) read
putStrLn "Enter several Int values, the last of which is 0 ..."
accum'' 0 (\num current -> current + (num :: Int)) (/= 0) read
putStrLn "Enter several Int values, the last of which is 0 ..."
accum'' 10000 (\num current -> min current (num :: Int)) (/= 0) read
putStrLn "Enter True True ... True False: "
accum'' 0 (\bool current -> current + 1) id read
putStrLn "That's the number of times you wrote True."
putStrLn "Enter several words, the last of which is STOP ..."
accum'' 0 (\str current -> current + numberOfEs str) (/= "STOP") id
24
Creating an instance of Read:
class MyClass a where
f :: a -> a
instance MyClass Int where
f n = n + 1
data MyString = MyString String
instance MyClass MyString where
f (MyString s) = MyString $ s ++ "*"
instance Read MyString where
readsPrec _ str = [(MyString (str ++ "!"), "")]
{-
readsPrec :: Read a => Int -> String -> [(a, String)]
The Int argument is operator precedence of the enclosing context
Each tuple is of the form (parsed value, remaining string)
readsPrec returns a list containing only one tuple or,
in the no parse case, an empty list.
read s = fst (head (readsPrec 0 s))
-}
instance Show MyString where
show (MyString s) = s ++ "@"
main = do
n <- getLine
let m = (read n) :: Int
putStrLn $ show $ f m
str <- getLine
let mstr = (read str) :: MyString
putStrLn $ show $ f mstr
25
Generating values randomly:
import System.Random
{-
randomR takes a tuple of bounds and a generator. It returns a
random value and a new generator.
random runs randomR with the appropriate default arguments
randoms generates a list of random values using the bounds
expressed in random
newStdGen's value is a monad containing a generator
-}
main = do
print $ randomR ('a', 'z') (read "1" :: StdGen)
g <- newStdGen
print $ randomR ('a', 'z') g
print $ randomR ('a', 'z') g
print $ randomR ('a', 'z') (snd (randomR ('a', 'z') g))
print . take 10 $ (randomRs ('a', 'z') g)
print . take 5 $ (randomRs (1,100 :: Int) g)
Exercise Set 9:
A room has a room number, a capacity, and an occupancy (all Int values).
1. Repeatedly prompt the user for a room number and a room capacity. Stop prompting the user when the
user enters the value -1 for the room number. This gives you a list of rooms.
2. Repeatedly prompt the user for a number of guests (the number of guests who want a room).
Immediately after the user enters a number of guests, look for an empty room with the capacity for at
least that number of guests. Produce a new list of rooms in which the occupancy of room that you found
is equal to the number of guests who wanted a room. Display a "Sorry" message if there are no rooms
with sufficient capacity. Stop repeating when all rooms are filled.
Exercise Set 10:
Write a program that randomly generates a number from 1 to 10. Then the program repeatedly asks the user to
guess a number. The program stops running when the user guesses the number that was generated.
26
Exercise Solutions
Exercise Set 1
1. head b + 1
2. f2 x = head x + 1
3. head b + head (tail b)
4. f4 x = head x + head (tail x)
5. [head b, head (tail b)]
6. f6 x = [head b, head (tail b)]
7. tail (tail b)
8. If you write tail tail b, then you start by working from left to right, trying to do tail tail
before you involve b in the evaluation. But there's no such thing as tail tail because then you're
trying to take the "tail of tail" but you can't take the tail of a function. You can only take the tail of a list.
9. In that expression, you first take the head of b, which is going to be a single element; namely, the
element 5. This element 5 is not a list of any kind. So you can't take the tail of that single element. You
can only take the tail of a list.
10. f10 = tail . tail
11. In the expression head + 1, you're trying to add two things that can't be added together. The
first, head, is a function and the second, 1, is a number.
12. f2 = ((+) 1) . head
Explanation: To turn the + operator into a function that can go before its argument, put the + sign in
parentheses, getting (+).
Then, remember that a function takes only one argument. So ((+) 1) is a new function that adds 1 to
anything you apply it to. In class, we used the example (max 7)
In that example, (max 7) is a new function that returns the maximum of 7 and anything that you
apply (max 7) to. So (max 7) 3 is 7, and (max 7) 9 is 9.
Finally, the composition ((+) 1) . head applies the head function first, and then applies the
((+) 1) function.
By the way, you could also write this function the following two ways: f2 = (1+) . head
f2 = (+1) . head
27
Exercise Set 2
emp1 = ("Joe", 10000.00)
emp2 = ("Jane", 30000.00)
emp3 = ("Mary", 5000.00)
emp4 = ("Ed", 25000.00)
emps = [emp1, emp2, emp3, emp4]
1. name x = fst x
salary x = snd x
2. map name emps
map salary emps
3. map (\x -> (salary x, name x)) emps
4. foldl (+) 0.0 $ map salary emps
5. filter (\x -> salary x <= 20000.00) emps
6. map (\x -> salary x + 500.00) (filter (\x -> salary x <= 20000.00) emps)
7. foldl (+) 0.0
(map (\x -> salary x + 500.00) (filter (\x -> salary x <= 20000.00) emps))
8. foldl (+) 0.0
(map (\x -> 500.00) (filter (\x -> salary x <= 20000.00) emps))
9. foldl (+) 0.0 $ map
(\x -> if salary x <= 20000.00 then salary x + 500.00 else salary x) emps
28
Exercise Set 3
1. zip [] _ = [] ; zip _ [] = [] ; zip (x:xs) (y:ys) = x : y : zip xs ys
zip "Hello" "Barry"
2. replicate 0 x = []
replicate n x = x:(replicate (n-1) x)
replicate 7 2
3. merge [] list = list
merge list [] = list
merge (x:xs) (y:ys) = if x >= y then y : (merge (x:xs) ys)
else x : (merge xs (y:ys))
merge [1,4,7,9] [3,5,8,9]
merge [1,4,7,9] [3,5,8,9, 10, 22]
4. flip [] = []
flip [a] = [a]
flip (x:y:xs) = y:x:(flip xs)
flip [1,2,3,4,5,6]
flip [1,2,3,4,5,6,7]
5. isSorted [] = True
isSorted [a] = True
isSorted [a,b] = a <= b
isSorted (x:y:xs) = x <= y && isSorted (y:xs)
isSorted [2,3,5,2,6,8]
isSorted [0,1,2,4,6,8]
6. small [a] = a
small (x:xs) = if x <= small xs then x else small xs
small [4,2,7,5,8]
small [4,2,7,5,8, 0]
small [0, 4,2,7,5,8, 5]
7. removeSmall [a] = []
removeSmall (x:xs) = if small (x:xs) == x then xs
else (x:(removeSmall xs))
removeSmall [0, 4,2,7,5,8, 5]
removeSmall [4,2,7,5,8, 5]
29
removeSmall [4,2,7,5,8, 5, 1]
8. ssort [] = [] ; ssort list = small list : ssort (removeSmall list)
ssort [4,2,6,8,3,5]
30
Exercise Set 4
type Length = Double
type Width = Double
type Height = Double
data Block = Block {lengthB :: Length, widthB :: Width, heightB :: Height}
deriving (Show)
block1 = Block 1.0 1.0 1.0
block2 = Block 2.0 3.0 4.0
block3 = Block 10.0 3.0 5.5
blocks = [block1, block2, block3]
volume (Block l w h) = l * w * h
volume2 b = lengthB b * widthB b * heightB b
averageVol list =
foldl (+) 0.0 (map volume list) / fromIntegral (length list)
volume1Block block =
Block ((lengthB block)/(volume block)**(1/3))
((widthB block)/(volume block)**(1/3))
((heightB block)/(volume block)**(1/3))
volume1s list = map volume1Block list
Exercise Set 5
import Text.Printf (printf)
type Name = String
type Price = Double
data Item = Item {nameItem :: Name, price :: Price} deriving (Show)
type CreditCardNumber = Int
data Customer =
Customer {nameCust :: Name, creditCardNumber :: CreditCardNumber}
deriving (Show)
data Purchase = Purchase {customer :: Customer, items :: [Item]}
deriving (Show)
item1 = Item "Shirt" 30.00
item2 = Item "Socks" 10.00
item3 = Item "Computer" 1000.00
item4 = Item "Meal" 17.50
item5 = Item "Mouse" 10.00
cust1 = Customer "Barry" 1111222233334444
cust2 = Customer "Jane" 2222333344445555
cust3 = Customer "Mary" 3333444455556666
31
purchase1 = Purchase cust1 [item1, item2, item3]
purchase2 = Purchase cust2 [item4, item5]
purchase3 = Purchase cust3 [item3]
purchasesForToday = [purchase1, purchase2, purchase3]
total purchases = foldl (+) 0.00 $ map price (concat (map items purchases))
purchaseTotal purchase = foldl (+) 0.0 (map price (items purchase))
bigPurchases purchases =
filter (\purch -> purchaseTotal purch > 100.00) purchases
bigCustomers purchases = map customer $ bigPurchases purchases
namesOfBigCustomers purchases = map nameCust (bigCustomers purchases)
messagesToBigCustomers purchases =
map (\string -> "Dear " ++ string ++ ", You've spent more than $100.00.")
$ namesOfBigCustomers purchases
betterMessagesToBigCustomers purchases =
map (\purchase -> "Dear " ++ nameCust (customer purchase) ++
", You've purchased $" ++ show (purchaseTotal purchase) ++ " in goods.")
$ bigPurchases purchases
better2 purchases =
map mailing bigOnes
where bigOnes = bigPurchases purchases
mailing purchase =
salutation ++ firstName purchase ++ message purchase
salutation = "Dear "
firstName purchase =
nameCust (customer purchase)
message purchase = ", You've purchased $"
++ show (purchaseTotal purchase)
++ " in goods."
better3 purchases = let bigOnes = bigPurchases purchases
mailing purchase =
salutation ++ firstName purchase ++ message purchase
salutation = "Dear "
firstName purchase = nameCust (customer purchase)
message purchase =
", You've purchased $"
++ show (purchaseTotal purchase) ++ " in goods."
in map mailing bigOnes
32
Exercise Set 6
adjIdent list = if length list == 0 || length list == 1
then False
else if head list == head (tail list) then True
else adjIdent (tail list)
33
Exercise Set 7
module Conversions where
import Text.Printf
class Convertible a where
toUS :: a -> a
data Locale = US | France | England deriving (Show)
--data ValueWithUnits v u = ValueWithUnits { value :: v, units :: u }
-- deriving (Show)
data Currency = Currency { locale :: Locale, amount :: Double }
instance Show Currency where
show (Currency US x) = "$" ++ printf "%5.2f" x
show (Currency France x) = "E" ++ printf "%5.2f" x
show (Currency England x) = "L" ++ printf "%5.2f" x
instance Convertible Currency where
toUS (Currency US x) = Currency US x
toUS (Currency France x) = Currency US (x * 1.0638)
toUS (Currency England x) = Currency US (x * 1.25)
data Units = Feet | Meters
data Length = Length { len :: Double, units :: Units }
instance Show Length where
show (Length x Feet) = show x ++ "ft"
show (Length x Meters) = show x ++ "m"
instance Convertible Length where
toUS (Length x Feet) = Length x Feet
toUS (Length x Meters) = Length (x * 3.28) Feet
fromUS (Length x Feet) = Length (x * 0.3048) Meters
fromUS x = x
instance Eq Currency where
a == b = abs (amount (toUS a) - amount (toUS b)) < 0.01
instance Eq Length where
a == b = abs (len (toUS a) - len (toUS b)) < 0.01
population US = 322762018
population England = 55040000
population France = 738849002
data HowManyPeople = HowManyPeople { number :: Double, place :: Locale }
34
instance Show HowManyPeople where
show (HowManyPeople d loc) = printf "%9.0f" d ++ " people in " ++ show loc
instance Convertible HowManyPeople where
toUS (HowManyPeople p US) = HowManyPeople p US
toUS (HowManyPeople p England) =
HowManyPeople (p * (population US) / (population England)) US
toUS (HowManyPeople p France) =
HowManyPeople (p * (population US) / (population France)) France
instance Eq HowManyPeople where
a == b = abs (number (toUS a) - number (toUS b)) < 100
data Scale = FAHR | CELS
instance Show Scale where
show FAHR = "Fahrenheit"
show CELS = "Celsius"
data Temp = Temp { value :: Double, scale :: Scale }
instance Show Temp where
show (Temp n s) = show n ++ " degrees " ++ show s
instance Convertible Temp where
toUS (Temp n FAHR) = Temp n FAHR
toUS (Temp n CELS) = Temp (n * 9/5 + 32) FAHR
instance Eq Temp where
temp1 == temp2 = abs (value (toUS temp1) - value (toUS temp2)) <= 0.01
main = do
putStrLn $ show $ Currency England 5
putStrLn $ show $ Currency US 10
putStrLn $ show $ Currency France 15
putStrLn $ show $ Currency England 1 == Currency US 1.25 --True
putStrLn $ show $ Currency England 1 == Currency France 1.17 --True
putStrLn $ show $ Currency England 1 == Currency US 1 --False
putStrLn $ show $ Length 7.5 Meters
putStrLn $ show $ Length 18 Feet
putStrLn $ show $ Length 1 Meters == Length 3.28 Feet --True
putStrLn $ show $ fromUS (Length 2 Feet)
putStrLn $ show $ fromUS (Length 2 Meters)
putStrLn $ show $ (Length 2 Feet) == (Length 0.609 Meters) --True
putStrLn $ show $ len (toUS (Length 2 Feet))
putStrLn $ show $ len (toUS (Length 0.609 Meters))
putStrLn $ show $
HowManyPeople 322762018 US == HowManyPeople 738849002 France --True
putStrLn $ show $
HowManyPeople 107587339 US == HowManyPeople 18346666 England --True
putStrLn $ show $ HowManyPeople 107587339 US
putStrLn $ show $ toUS (HowManyPeople 18346666 England)
35
putStrLn $ show $ Temp 32 FAHR
putStrLn $ show $ toUS (Temp 100 CELS)
putStrLn $ show $ Temp 100 CELS == Temp 212 FAHR --True
putStrLn $ show $ Temp 0 CELS == Temp 32 FAHR --True
putStrLn $ show $ Temp 12 CELS == Temp 32 FAHR --False
36
Exercise Set 8
waitingTime arrivalTime seatedTime = seatedTime - arrivalTime
maybeWaitingTime arrivalTime seatedTime =
if arrivalTime < seatedTime
then Just (seatedTime - arrivalTime)
else Nothing
maybeServiceWasOK waitingTime =
Just $ waitingTime >= 0 && waitingTime <= 30
question4 arrivalTime seatedTime =
maybeWaitingTime arrivalTime seatedTime >>= maybeServiceWasOK
maybeShowBoolean True = Just "Good Service"
maybeShowBoolean False = Just "Bad Service"
question6 arrivalTime seatedTime =
return seatedTime >>= maybeWaitingTime arrivalTime >>=
maybeServiceWasOK >>= maybeShowBoolean
main = do
print $ maybeWaitingTime 600 625
print $ maybeWaitingTime 625 600
print $ question4 600 625
print $ question4 600 635
print $ question4 700 600
print $ question6 600 625
print $ question6 600 635
print $ question6 700 600
37
Exercise Set 9
type RoomNumber = Int
type Capacity = Int
type Occupancy = Int
data Room = Room {roomNumber::RoomNumber, capacity::Capacity,
occupancy::Occupancy}
instance Show Room where
show room = "Room " ++ show (roomNumber room) ++
": capacity " ++ show (capacity room) ++ " occupancy " ++
show (occupancy room)
makeRoomList :: [Room] -> IO [Room]
makeRoomList currentList = do
putStr "Enter a room number (or -1 to finish): "
roomNumberString <- getLine
let roomNum = read roomNumberString :: Int
if roomNum == -1 then do
printNicely currentList
return currentList
else do
putStr "What's that room's capacity? "
capString <- getLine
let cap = read capString :: Int
makeRoomList $
currentList ++ [Room roomNum cap 0]
printNicely list = mapM_ print list
fillRoom whichRoom numberOfPeople listOfRooms =
take whichRoom listOfRooms ++
[Room (roomNumber oldRoom) (capacity oldRoom) (numberOfPeople)] ++
drop (whichRoom + 1) listOfRooms
where oldRoom = listOfRooms!!whichRoom
findVacancies listOfRooms = do
if all (/= 0) (map occupancy listOfRooms)
then putStrLn "\nSorry! No vacancies...Exiting the program"
else do putStr $ "\nHow many people do you want to put into a room? "
howMany <- getLine
let numberOfPeople = read howMany :: Int
if numberOfPeople == 0 then putStrLn "Exiting the program"
else findVacancy
0 listOfRooms numberOfPeople
findVacancy whichRoom listOfRooms numberOfPeople = do
if whichRoom >= length listOfRooms
then do
putStrLn
"Sorry. We don't have sufficient capacity for your group."
findVacancies listOfRooms
38
else if occupancy roomUnderConsideration /= 0 ||
numberOfPeople > capacity roomUnderConsideration
then findVacancy (whichRoom + 1) listOfRooms numberOfPeople
else do let newListOfRooms =
fillRoom whichRoom numberOfPeople listOfRooms
putStrLn "\n"
printNicely newListOfRooms
findVacancies newListOfRooms
where roomUnderConsideration = listOfRooms!!whichRoom
main = makeRoomList [] >>= findVacancies
39
Exercise Set 10
import System.Random
prompt correctGuess = do
putStr "Guess a number from 1 to 10: "
currentGuessStr <- getLine
let currentGuess = read currentGuessStr :: Int
if currentGuess == correctGuess
then putStrLn "You win!"
else do
putStrLn "Incorrect..."
prompt correctGuess
main = do
g <- newStdGen
prompt $ fst (randomR (1::Int,10) g)
Recommended