Programs must be written for people to read, and only incidentally for machines to execute. - SICP
Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live. - c2.com
Style is important! Programs having remarkably long lifespans. Sometimes they even outlive their creators (!). A program will almost always be modified, extended and repaired countless times by many different people, so it is important that we write code that will be easily readable by other programmers.
If you don't think that's a good excuse, consider that if a tutor can't figure out what your program does, then you won't get full credit. You (the brilliant student you are) want to make your grader's life as easy as possible; don't tempt fate!
Let's look at some guidelines on how to write clear, readable code in DrRacket.
Consider the data definition for Time
:
;; A Time is (make-time Number Number) (define-struct time (hours minutes))
Consider this program:
;; Time -> Image ;; Produce an image of the given time, as it appears on a digital clock. (define(time->text a-time) (text(string-append(number->string(time-hours a-time))":"(cond[(< (time-minutes a-time)10)"0"] [else ""])(number->string(time-minutes a-time)))30'red))
text
?cond
clauses are there?string-append
?;; time->string: Time -> String ;; Produce a string of the given time, as it appears on a digital clock. (define (time->string a-time) (string-append (number->string (time-hours a-time)) ":" (cond [(< (time-minutes a-time) 10) "0"] [else ""]) (number->string (time-minutes a-time)))
With this code, it is easy to see the three arguments to;; time->image: Time -> Image ;; Produce an image of the given Time. (define (time->image a-time) (text (time->string a-time) 30 'red))
text
, the four strings being appended, and the two cond
clauses.
In general, try to break long lines by
cond
clause its own line. [Always a
good idea.]cond
question or answer is long, then
start the answer on the next line. [Often a good idea.]string-append
is a typical offender.]time->text
above, it's probably a good idea to factor out the
(string-append ..)
expression as a separate helper
function.
Every line of code should be no more than 80 characters long. DrRacket can help you with this — Can you find where it indicates the current column of the cursor?
distance->text
function by
developing and using helper functions that compute the
(string-append ...)
and conditional
portions of the body. Be sure to
invent a good name for these helper functions.
;; A Distance is (make-distance Number Number) (define-struct distance (feet inches)) ;; Where feet is a Number from 0 - 99 and inches is ;; a Number from 0 - 12.
;; Distance -> Image ;; Produce an image of the given distance, as it appears on a digital interface. ;; Ex. (make-distance 5 9) -> '05ft09in' ;; (make-distance 12 10) -> '12ft10in' (define(distance->text a-distance) (text(string-append(cond[(< (distance-feet a-distance)10)"0"] [else ""])(number->string(distance-feet a-distance))"ft" (cond[(< (distance-inches a-distance)10)"0"][else ""]) (number->string(distance- inches a-distance))"in")30'red))
Consider the following program:
This is not indented properly. Copy and paste this code into DrRacket. Then select the code and hit the;; LOS -> Number ;; Determine how many symbols are in a-los (define (count a-los) (cond [(empty? a-los)0] [(cons? a-los) (+ 1 (count (rest a-los)))]))
Tab
key.
DrRacket will indent all the selected code automatically!
(Note that this will not help you with long lines and/or line
breaks.
Make good use of this feature as you develop your programs.
Also note that the Tab
key can be used to
automatically indent the line the cursor is currently on.
Indentation is a very important factor of readability because it
denotes the structure of the program at a glance. And we
can't grade what we can't read!!
Let's reconsider count
from above. The
indentation is technically correct, but the parentheses are
arranged a poorly:
;; LOS -> Number ;; Determine how many symbols are in a-los (define (count a-los) (cond [(empty? a-los) 0 ] [(cons? a-los ) (+ 1 (count (rest a-los) ) ) ] ) )
A programmer who arranges their parentheses like this is probably trying to use the vertical alignment of the open and closing parentheses to visually determine the code structure. It is much easier to compress the closing parentheses together, and then eyeball the program structure using its indentation. When you need to match parentheses visually, use DrRacket's grey highlighting instead.
;; LOS -> Number ;; Determine how many symbols are in a-los (define (count a-los) (cond [(empty? a-los) 0] [(cons? a-los) (+ 1 (count (rest a-los)))]))
Proper indentation and parentheses placement render the parentheses and brackets nearly invisible to the trained eye.
We want you all to be able to write recursive functions as easily
as (list 1 2 3)
, so we're going to practice.
Please write each of the requested functions from scratch.
Design a function, string-repeat
,
that takes a positive number (n) and a string,
and returns a string that contains the given string
repeated n times, separated by a space.
Examples:
(string-repeat 4 "Test") ;==> "Test Test Test Test" (string-repeat 2 "What") ;==> "What What"
Using your function above as a helper, create another
function, reducing
that takes a number and a string,
and returns a list of strings. Each element of the list is the
string returned from string-repeat
with a reduced n
Examples:
(reducing 4 "Test") ;==> (list "Test Test Test Test" "Test Test Test" "Test Test" "Test") (reducing 2 "What") ;==> (list "What What" "What")
Now design the function lookup
that takes
a list of Symbols los, and a number n,
and returns the nth symbol of the list.
Examples:
(lookup (list 'a 'b 'c 'd) 0) ;==> 'a (lookup (list 'a 'b 'c 'd) 2) ;==> 'c
Next design the function replace
that takes
a list of Symbols los, a symbol s, and
a a number n. The function returns los with
the nth symbol replaced with s.
Examples:
(replace (list 'a 'b 'c 'd) 'new 2) ;==> (list 'a 'b 'new 'd) (replace (list 'a 'b 'c 'd) 'yay 0) ;==> (list 'yay 'b 'c 'd) (replace (list 'a 'b 'c 'd) 'end 3) ;==> (list 'a 'b 'c 'end)
Consider the following problem:
Given two lists of strings, return a list of strings that contains all combinations of elements from the first list with elements from the second list.
Let's call the function all-comb
. Here's an example:
;; This example (all-comb (list "Student: " "Faculty: ") (list "Mr." "Ms." "Mrs.")) ;; Results in... (list "Student: Mr." "Student: Ms." "Student: Mrs." "Faculty: Mr." "Faculty: Ms." "Faculty: Mrs.")
How can we design such a function? Well, lets start with a smaller problem. How can we take a string, s, and a list-of-strings, los, and produce a list that contains the strings from los with s on the front.
Go for it!! Call this
function all-comb-help
.
Here's an example:
(all-comb-help "A" (list "B" "C" "D")) ;==> (list "AB" "AC" "AD")
Now... how can you put the helper function to work to solve
the entire problem? Ask a TA/Tutor if you need
help. Hint: you can use append
(or define your
own for practice), which appends two lists.
Challenge:
Can you do the above problem without using append
?
The design recipe is a powerful tool, but it only works when used properly. The staff has identified a number of common errors that have been showing up on homeworks and the midterm. Let's go over a few of them in detail.If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra
;; only-evens : list-of-numbers -> list-of-numbers ;; to create a list containing only the even numbers in a-list-of-nums (define (only-evens a-list-of-nums) (cond [(empty? a-list-of-nums) 0] [(even? (first a-list-of-nums)) (cons (first a-list-of-nums) (only-evens (rest a-list-of-nums)))] [else (only-evens (rest a-list-of-nums))]))
There is a bug in the definition above. The first branch of
the cond
clause is violating the contract. 0
is a number, not a list of numbers. In its place we should be
using empty
. By carefully making sure each branch of
our cond
statements satisfy our contract we can avoid
such errors.
A contract is only as useful as the information it provides. If we fail to fully specify the kinds of data our functions consume and produce then we defeat the purpose of the contract. Consider the following example.
;; name->greeting : name -> greeting
;; to create a greeting from the provided name
It would make sense to assume that name and greeting are
strings
. We could write the following function for the
contract:
(define (name->greeting name) (string-append "Hello, " name "!"))
But what if some other part of our program thought that name
was a structure, (define-struct name (first last))
, an
equally reasonable assumption? We can only avoid such errors by
providing data definitions for each kind of data our
functions consume and produce.
The following data definition clears up the ambiguity:
;; name->greeting : Name -> string
;; to create a greeting from the provided name
;;
;; a Name is a structure: (make-name first last) where first and last
;; are strings.
Identify whether there is a contract violation or a lack of a data definition. Correct the problem, fixing the code if necessary.
;; a Dog is (define-struct dog (name age breed)) where name is a ;; string, age is a number and breed is a symbol (define-struct dog (name age breed)) ;; dogs-older-than : list-of-Dogs number -> list-of-Dogs ;; to list all the dogs older than age (define (dogs-older-than dogs age) (cond [(empty? dogs) empty] [(> (dog-age (first dogs)) age) (cons (dog-name (first dogs)) (dogs-older-than (rest dogs) age))] [else (dogs-older-than (rest dogs) age)]))
Identify whether there is a contract violation or a lack of a data definition. Correct the problem, fixing the code if necessary.
;; numbers-between : list-of-numbers -> number-range ;; to list all the numbers between the low number and the high number (define (numbers-between low high) (cond [(> low high) empty] [else (cons low (numbers-between (add1 low) high))]))
Challenge: Identify whether there is a contract violation or a lack of a data definition. Correct the problem, fixing the code if necessary.
;; pairify : list-of-any -> list-of-list ;; to group the elements of the input list into a list of two-element lists (define (pairify a-list) (cond [(or (empty? a-list) (empty? (rest a-list))) empty] [else (cons (cons (first a-list) (first (rest a-list))) (pairify (rest (rest a-list))))]))