Page 1

Page 2

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 3

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`
?
Page 4

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 5

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 6

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 1,000 elements!
Page 7

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 8

What is a Queue?

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

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
```
Page 10

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 11

Simple Queue efficiency?

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

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 13

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 14

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 15

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 16

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 17

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 18

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 19

• ```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 20

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.