- 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 ? O(
`xs``++``ys``n`), where`n`=`length xs`

- How much work in total to compute ? O(
`reverse``xs``n`^{2})

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

- 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*.

- =
`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 now? O(
`reverse``xs``n`)

`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!

*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

dataQa-- the type of queuesempty::Qa-- an empty queueadd::a->Qa->Qa-- add an element at the backisEmpty::Qa->Bool-- check if the queue is emptyfront::Qa->a-- inspect the front elementremove::Qa->Qa-- remove an element from the front

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

- How much work to compute
`add`5 (`add`4 (`add`3 (`add`2 (`add`1`empty`))))

- Looks familiar?

**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 to move elements from the back list to the front list when the front list becomes empty.
`reverse`

`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*.

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

`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 enforces the invariant and simplifies the code.
`smartQ`- Fewer cases to consider.

- How much work is needed when
- adding an element? O(1)
- removing an element? O(1)
- reversing lists? O(1)

- Key observation:
- each added elements is only moved from the back to the front once.

- 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 instance, these queues would be considered different.
**Eq** - So we define our own instance:
**Eq****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?

**module****Queue**(**Q**,`empty`,`add`,`remove`,`front`,`isEmpty`)**where****data****Q**`a`**=****Q**[`a`] [`a`]**deriving****Show**`empty`**=**`smartQ`[] [] -- ... -- ...- We export the type but not the constructor
**Q****Q**- to prevent clients from breaking the invariant.

- We don't export the helper functions and
`smartQ`.`toList` - See the complete module in Queue.hs.

**module****Queue**(**Q**,…)**where**- This exports the type but not its constructors,
**Q** - so will be an
**Q***abstract type*outside this module.

- This exports the type
**module****Queue**(**Q**(..),…)**where**- This would export the type and all its constructors.
**Q**

- This would export the type
**module****Queue****where**- This would export everything (all types and functions).

**module****Queue**()**where**- No functions or types would be exported. (Rarely useful.)

- 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.