Function Design Revisited

Saturday, Mar 19, 2022
Racket

Earlier I wrote about the key steps in a function design. Now after some experience writing functions using the design recipe, I want to revise the recipe based on what I learnt and extend that to Racket.

Recipe Revision

Step 1: Signature, Purpose and Stub

Step 2: Examples as Tests

Step 3: Template and Inventory

Step 4: Write the Function Body

Step 5: Test and Iterate until all tests pass

Step 6: Review and Clean

Let's try to look at these steps through a simple design exercise - Design a function to check if length of a string is less than 5.

Racket Example

So we need a function that consumes a string and produces a boolean. So following will be the signature:

;; String -> Boolean

Adding a one line purpose, our code becomes:

;; String -> Boolean
;; Produce true if given string length is less than 5

Now let's think of stub, less-than-5? is a good function name for this problem and str as the parameter. Note that we arbitrarily picked false as return value - it just has to match the type of return while the actual value is unimportant.

;; String -> Boolean
;; Produce true if given string length is less than 5
(define (less-than-5? str) false)

Now let's think about some examples. Since it returns boolean, we want at least two examples - one with false and other with true as return value:

;; String -> Boolean
;; Produce true if given string length is less than 5
(define (less-than-5? str) false)

;; Tests
(check-true (less-than-5? "hi") "string length less than 5")
(check-false (less-than-5? "hi there!")"string length is not less than 5")

We run our tests to make sure they are well formed. These are expected to fail (see below) since we have not coded the body of our function.

--------------------
. FAILURE
name:       check-true
location:   htdf-test.rkt:11:0
params:     '(#f)
message:    "string length less than 5"
--------------------

Ok, now we write the template and comment the stub:

;; String -> Boolean
;; Produce true if given string length is less than 5
;(define (less-than-5? str) false)
(define (less-than-5? str)
  (... str))

;; Tests
(check-true (less-than-5? "hi") "string length less than 5")
(check-false (less-than-5? "hi there!")"string length is not less than 5")

Now we can think about what should go in ... place in the template that likely uses str parameter. In this case the body is simple:

;; String -> Boolean
;; Produce true if given string length is less than 5
;(define (less-than-5? str) false)
(define (less-than-5? str)
  (< (string-length str) 5)

;; Tests
(check-true (less-than-5? "hi") "string length less than 5")
(check-false (less-than-5? "hi there!")"string length is not less than 5")

Our tests pass but in the last step we review our code for readability and see if it can be improved. Observing the purpose, we note that an edge case is not tested where string length is equal to 5. We should produce false in that case. We have also removed the stub now.

;; String -> Boolean
;; Produce true if given string length is less than 5
(define (less-than-5? str)
  (< (string-length str) 5)

;; Tests
(check-true (less-than-5? "hi") "string length less than 5")
(check-false (less-than-5? "hello") "string length is equal to 5")
(check-false (less-than-5? "hi there!")"string length is not less than 5")

All tests pass!