Skip to main content

Lab: Verifying preconditions

Held
Friday, 15 February 2019
Writeup due
Monday, 18 February 2019
Summary
In this laboratory, you will consider mechanisms for verifying the preconditions of procedures. You will also consider some issues in the documentation of such procedures.

Preparation

Do the normal preparation for a lab. That is,

  • Open a terminal window and type /home/rebelsky/bin/csc151/update.
  • Start DrRacket.
  • Require the loudhum package with (require loudhum).

Exercises

Exercise 1: Exploring preconditions

Consider a procedure, (greatest-of-list lst), that finds the largest number in a list.

(define greatest-of-list
  (lambda (lst)
    (reduce max lst)))

a. What preconditions should greatest-of-list have?

b. What postconditions should greatest-of-list have?

c. Discuss your answers with a neighboring group. (Be prepared to discuss them with an instructor or mentor, too.)

Exercise 2: Averages

In the lab on list processing, you saw that you could compute the average of a list using a combination of reduce and length.

a. Write a procedure, (average lst), that averages the values in lst.

b. What preconditions should average have?

c. How are those preconditions similar to those of greatest-of-list? How do they differ?

d. Discuss your answers with a neighboring group. (Be prepared to discuss them with an instructor or mentor, too.)

Exercise 3: Detour: Thinking with lists

In thinking about the procedures that work on lists, such as map, reduce, tally, or sort, we often use the mantra “First solve for one or two elements, then solve for many”. That is, because each of these procedures takes a list and a procedure, our first focus should be on how that procedure works with individual elements or pairs of elements.

  • For map, we most frequently work with single elements, so we make sure our one-parameter procedure works and then map it over the list.
  • For reduce, we need a procedure that combines neighboring elements, so we develop a two-parameter procedure, check it on pairs of elements, and then use reduce to combine all of the elements in the list.
  • For tally and filter, we need a predicate that looks at a single value and returns true or false.
  • For sort, we need a predicate that looks at two values and returns true if the first should come before the second.

a. Suppose we have a list of strings and want to duplicate every string in the list.

> (map ??? '("a" "b" "c"))
'("aa" "bb" "cc")

What procedure should you use? (You may have to define it yourself.)

b. Suppose we want to combine all of the strings in a list with spaces between them. What procedure should you use along with reduce? (Once again, you may have to define it yourself.)

> (reduce ??? '("a" "b" "c"))
"a b c"

c. Suppose we want to sort a list of strings in order of increasing length. What procedure should you use along with sort? (Once again, you may have to define it yourself.)

> (sort '("longish" "short?" "a" "hello" "two") ???)
'("a" "two" "hello" "short?" "longish")

Exercise 4: List predicates

It seems that determining whether all of the elements of a list are a particular type, such as all integers or all strings, should use such an approach. It may feel like like we should be able to map a predicate onto the list to get a list of #t and #f and then reduce using and. Let’s try.

> (define reals (list 1 3.2 -5 8 23))
> (define odds (list 1 3 5 7 5 1 7 1))
> (define mixed (list "a" 23 'c 11))
> (map real? reals)
'(#t #t #t #t #t)
> (map real? odds)
'(#t #t #t #t #t #t #t #t)
> (map real? mixed)
'(#f #t #f #t)
> (map (conjoin integer? odd?) reals)
'(#t #f #t #f #t)
> (map (conjoin integer? odd?) odds)
'(#t #t #t #t #t #t #t #t)
> (map (conjoin odd? integer?) odds)
'(#t #t #t #t #t #t #t #t)
> (map (conjoin odd? integer?) odds)
'(#t #t #t #t #t #t #t #t)
> (map (conjoin odd? integer?) reals)
Error! odd?: contract violation
Error!   expected: integer
Error!   given: 3.2
; Note: Order of parameters to `conjoin` is important.

Now, how do you convert a list of #t and #f values to a single value? That feels like a job for reduce.

> (reduce and (list #t #t #f #t #t))
> (reduce and (map real? reals))
> (reduce and (map real? mixed))

a. Do you think this strategy will work? Why or why not?

b. Check your answer experimentally.

Exercise 5: List predicates, revisited

As you likely discovered, because and is a keyword, we cannot use it with reduce. Fortunately, Racket provides a procedure, (all pred? lst), that takes a unary predicate and checks each element of a list with that predicate.

a. Using and, check each of the cases we looked at in the previous exercise (e.g., see if reals, odds, and mixed contains only reals).

b. What do you expect as the results of the following?

> (all positive? reals)
> (all positive? odds)
> (define nums (list 1 2.3 4/5 6+7i))
> (all number? nums)
> (all real? nums)
> (all positive? nums)

c. Check your answers experimentally.

Exercise 6: Average, revisited

Rewrite your average procedure so that it checks its preconditions, reporting a separate message for each kind of failed precondition.

In case you’re not sure, we’d expect you to check the following preconditions.

  • The parameteer is a list.
  • The list is not empty.
  • The list contains only numbers.

For those with extra time

Extra 1: Longest strings

a. Write a procedure, (longer? str1 str2) that determines whether str is longer than str2`.

b. Using longer?, sort a list of strings by length.

c. Write a procedure, (longer str1 str2) that returns whichever of str1 and str2 is longer. (That is, longer acts much like max, except for strings.)

d. Using longer, write a procedure, (longest-kernel strings), that finds the longest string in a list of strings. As the name suggests, longest-kernel need not check its preconditions.

e. Write a procedure, (longest strings), that finds the longest string in a list of strings. Your procedure should check all of its preconditions and issue an appropriate error message for each.

Extra 2: Substitution

Consider a procedure, (list-substitute lst old new), that builds a new list by substituting new ... old whenever old appears in lst.

> (list-substitute (list "black" "red" "green" "blue" "black") "black" "white")
("white" "red" "green" "blue" "white")
> (list-substitute (list "black" "red" "green" "blue" "black") "yellow" "white")
("black" "red" "green" "blue" "black")
> (list-substitute null "yellow" "white")
()

a. Document this procedure, making sure to carefully consider the preconditions.

b. Write the husk for this procedure. (That is, just write the precondition tests. You can just return the original list for now.)