Page 1

Page 2

# Reminder

## Drop-in office hours

### For questions about the lab assignments

• Monday 15-16: Elisabet Lobo-Vesga, room 5453.
• Tuesday 15-16: Jeff/Yu-Ting Chen, room 5449.
• Wednesday 13.30-14.30: Herbert Lange, room 6113A.
Page 3

# Efficiency of reverse

• Consider reversing a list. Here is a simple solution:
• ```reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
```
• How many calls to
`++`
to reverse a list with n elements? n
• ```(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x:(xs ++ ys)
```
• How much work to compute
`xs++ys`
? O(n), where n = `length xs`
• How much work in total to compute
`reverse xs`
? O(n2)
Page 4

# Testing it in GHCi

• Use `:set +s` in GHCi to turn on time and memory stats.
• `:set +s`  `sum [1..10000]` 50005000 (0.01 secs, 2,633,512 bytes) `sum (reverse [1..10000])` 50005000 (1.56 secs, 4,455,178,368 bytes)
• How to speed up
`reverse`
?
• Live demo: Reverse.hs
Page 5

# Faster Reverse

• Idea: use an accumulating parameter
• ```reverse :: [a] -> [a]
reverse xs = revOnto xs []

-- revOnto xs rs = reverse xs ++ rs
revOnto :: [a] -> [a] -> [a]
revOnto []     rs = rs
revOnto (x:xs) rs = revOnto xs (x:rs)
```
• The helper function
`revOnto`
• moves the list elements onto an accumulating parameter, where they will appear in reverse order.
• is tail recursive.
Page 6

# Faster Reverse example

• `reverse [1,2,3]`
=
• `revOnto [1,2,3] []`
=
• `revOnto [2,3]  [1]`
=
• `revOnto [3]  [2,1]`
=
• `revOnto [] [3,2,1]`
=
• `[3,2,1]`
• How much work in total to compute
`reverse xs`
now? O(n)
Page 7

# Testing it in GHCi again

• `sum (reverse [1..10000])` 50005000 (0.01 secs, 3,916,656 bytes) `sum (reverse [1..1000000])` 500000500000 (0.89 secs, 386,901,184 bytes)
• This version can reverse 1,000,000 elements faster than the old version reversed 10,000 elements!
Page 8

# Data Structures

• Data Type
• A model of something that we want to represent in our program
• Data Structure
• A particular way of storing data
• What is a good choice? It depends on what we want to do with the data.
• This might be an implementation details that we want to hide.
• Today's example
• Queues
Page 9

# What is a Queue?

• Enter at the back, leave at the front
• Examples
• Files to print
• Network packets to send
• Processes to run
Page 10

# A Queue interface

• ```data Q a                    -- the type of queues

empty   :: Q a              -- an empty queue
add     :: a -> Q a -> Q a  -- add an element at the back

isEmpty :: Q a -> Bool      -- check if the queue is empty
front   :: Q a -> a         -- inspect the front element
remove  :: Q a -> Q a       -- remove an element from the front
```
• Clients will use the provided functions, but not the constructors of of the type
`Q`
, so no pattern matching.
Page 11

# First Queue implementation

• A simple solution
• ```data Q a = Q [a] deriving (Eq,Show)

empty              = Q []
add x   (Q xs)     = Q (xs++[x])

isEmpty (Q xs)     = null xs
front   (Q (x:xs)) = x
remove  (Q (x:xs)) = Q xs```
Page 12

# Simple Queue efficiency?

• How much work to compute
• `add 5 (add 4 (add 3 (add 2 (add 1 empty))))`
• Looks familiar?
Page 13

# New idea: use two lists

• `data Q a = Q [a] [a] deriving Show`
• A "back" list where we can quickly add new elements (last added element first).
• A "front" list where we can quickly remove elements (first added element first).
• Use efficient
`reverse`
to move elements from the back list to the front list when the front list becomes empty.
Page 14

# First attempt

• ```empty                = Q [] []
add x  (Q fs bs)     = Q fs (x:bs)

isEmpty (Q fs bs)    = null fs && null bs

front (Q (f:fs) bs)  = x
front (Q []     bs)  = head (reverse bs)

remove (Q (f:fs) bs) = Q fs bs
remove (Q []     bs) = Q (tail (reverse bs)) []
```
• If we allow the front list to become empty, we might duplicate work...
• Introduce an invariant: never construct a queue where the front is empty and the back is non-empty
• Enforce it by using a smart constructor.
Page 15

Page 16

# Smart constructor

• To enforce the invariant, we define the smart constructor
`smartQ`
and use it instead of directly using the constructor
`Q`
.
• ```smartQ [] bs = Q (reverse bs) []
smartQ fs bs = Q fs bs```
Page 17

# Operations

• ```empty                      = smartQ [] []
add x   (Q front back)     = smartQ front (x:back)

isEmpty (Q front back)     = null front
front   (Q (x:_) _)        = x
remove  (Q (x:front) back) = smartQ front back```
• Using
`smartQ`
enforces the invariant and simplifies the code.
• Fewer cases to consider.
Page 18

# Queue Efficiency

• How much work is needed when
• removing an element? O(1)
• reversing lists? O(1)
• Key observation:
• each added elements is only moved from the back to the front once.
Page 19

# Queue Equality

• The same queue can be represented in many ways, e.g.
• `Q [1] [4,3,2] == Q [1,2] [4,3] == Q [1,2,3,4] []`
• But with the derived
`Eq`
instance, these queues would be considered different.
• So we define our own
`Eq`
instance:
• ```instance Eq a => Eq (Q a) where
q1 == q2 = toList q1 == toList q2

toList (Q front back) = front ++ reverse back```
• But what type of applications need to test if queues are equal? Maybe this unnecessary?
Page 20

# Modules and abstract data types

## Modules can be used to hide implementation details

• ```module Queue(Q,empty,add,remove,front,isEmpty) where

data Q a = Q [a] [a] deriving Show

empty = smartQ [] []
-- ...
-- ...
```
• We export the type
`Q`
but not the constructor
`Q`
• to prevent clients from breaking the invariant.
• We don't export the helper functions
`smartQ`
and
`toList`
.
• See the complete module in Queue.hs.
Page 21

• ```module Queue(Q,…) where```
• This exports the type
`Q`
but not its constructors,
• so
`Q`
will be an abstract type outside this module.
• ```module Queue(Q(..),…) where```
• This would export the type
`Q`
and all its constructors.
• `module Queue where`
• This would export everything (all types and functions).
• `module Queue() where`
• No functions or types would be exported. (Rarely useful.)
Page 22

# Correctness and testing

• Use the simple implementation as a reference.
• One hopes that simple means "obviously correct".
• The more sophisticated implementation should behave the same as the reference implementation.
Page 23