Skip to main content

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 of every-third-element, it is put into the top-level program directory. This means that there is only ever one cnt, and separate calls to every-third-element share the same cnt.
  • This means two things, both of which are very bad:
    1. 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.
    2. 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)