Day 17 - Testing and variables that change
Skills: 3
Pre-reading: 9.1.8.5.4
Intro (15 mins)
- All our uses of mutable variables have intentionally followed the same pattern:
fun func(lst :: List) block:
var result = ... # what to return if the input list is empty
for each(item from lst):
# combine item with the result so far
result := ... item ... result
end
result
end - In particular, we only use
var
inside of functions. Why? - Consider we wanted to implement a function
every-third-element
that took a list, and returned the first, fourth, seventh, etc, elements. - If we wrote it as:
var cnt = 0
fun every-third-element(lst :: List) -> List block:
var output = [list: ]
for each(elt from lst) block:
when num-modulo(cnt, 3) == 0:
output := output + [list: elt]
end
cnt := cnt + 1
end
output
where:
every-third-element([list: ]) is [list: ]
every-third-element([list: 1, 3, 5]) is [list: 1]
every-third-element([list: 1,3,5,7,11]) is [list: 1, 7]
end - It seems to work. But then we actually use it, adding
every-third-element([list: 1,3])
somewhere in our code, and suddenly two of our tests are now failing. Why is this happening? - Since
cnt
is defined outside ofevery-third-element
, it is put into the top-level program directory. This means that there is only ever onecnt
, and separate calls toevery-third-element
share the samecnt
. - This means two things, both of which are very bad:
- The function no longer returns the same output depending on the same input. Instead, the output depends on the input and the current state of cnt.
- This means, more confusingly, that to understand what a given call of
every-third-element
does, we need to know all the calls that have happened before, as the program ran, which may not be obvious from reading the program (since with looping, with one function calling others, etc, the order and how many times a function is called can be tricky to figure out).
Class Exercises (40 mins)
-
What does each call return? (Trace very carefully through the execution order):
var score = 10
fun bonus-points(amt :: Number) -> Number block:
score := score + amt
penalty-check()
end
fun penalty-check() -> Number block:
when score > 15:
score := score - 3
end
score
end
bonus-points(2)
bonus-points(4)
bonus-points(1) -
Trace through these function calls. What does
process-data
return each time?var dta = [list: 1, 2, 3]
var position = 0
fun advance() -> Number block:
result = dta.get(position)
position := position + 1
when position >= dta.length():
position := 0
dta := [list: 0] + dta
end
result
end
fun process-data() -> Number block:
first = advance()
second = advance()
first + second
end
process-data()
process-data()
process-data() -
Trace execution. What does
calculate
return for each call?
var multiplier = 1 var base = 5 fun update-multiplier(n :: Number) -> Number block: multiplier := multiplier + n when num-modulo(multiplier, 3) == 0 block: base := base - 1 multiplier := 1 end multiplier end fun calculate(x :: Number) -> Number block: update-multiplier(x) (x * multiplier) + base end
calculate(2) calculate(1) calculate(4) calculate(2)
- Now, pick one of the examples above, and try to write tests for it using a `where:` block. What goes wrong?
## Wrap-up (5 mins)