The goal for this section is to define some guidelines on how code in the Student Languages should be formatted.
In one sense, none of this stuff is foundational to computer science; a typical computer will not discriminate between a horribly formatted program and a nicely formatted one.
But on the other hand, computer science is about people communicating with each other; you should take pride in how you present your code, because it is going to affect how people read it. Get in the good graces of your grader!
Consider the following definitions
of create-address.
;; create-address: DBR -> string
;; create a three line address string from a db
(define(create-address DBR)
(string-append (db-prefix DBR) " " (db-first DBR) " " (make-string 1
(db-middle DBR)) " " (db-last DBR) "\n"
(db-street DBR) "\n"
(db-city DBR) " " (db-state DBR) " " (db-zip DBR)))
|
;; create-address: DBR -> string
;; create a three line address string from a db
(define (create-address DBR)
(string-append (db-prefix DBR) " "
(db-first DBR) " "
(make-string 1 (db-middle DBR)) " "
(db-last DBR) "\n"
(db-street DBR) "\n"
(db-city DBR) " "
(db-state DBR) " "
(db-zip DBR)))
|
string-append function invocation? Is it (db-middle DBR),
as in
(string-append
(db-prefix DBR) " " (db-first DBR) " " (make-string 1
(db-middle DBR))
Compare the following definitions for convert-DBRs.
Reminder:
;; A LoDBR is One of: ;; - empty ;; - (cons DBR LoDBR) ;; A LoADD is one of: ;; - empty ;; - (cons String LoADD) |
;; convert-DBRs: LoDBR -> LoADD
;; converts LoDBR into LoADD
(define (convert-DBRs a-LoDBR)
(cons
(string-append
(db-prefix (first a-LoDBR)) " " (db-first (first a-LoDBR)) " "
(db-middle (first a-LoDBR)) " " (db-last (first a-LoDBR)) "\n"
(number->string(db-no (first a-LoDBR))) " " (db-street (first a-LoDBR)) "\n"
(db-city (first a-LoDBR)) ", " (db-state (first a-LoDBR)) " "
(number->string(db-zip (first a-LoDBR))) " "
(number->string(db-priority (first a-LoDBR))))
(cond
[(empty? (rest a-LoDBR)) empty]
[else
(convert-DBRs (rest a-LoDBR))])))
|
;; convert-DBRs: LoDBR -> LoADD
;; converts LoDBR into LoADD
(define (convert-DBRs a-LoDBR)
(local ((define the-string
(string-append (db-prefix (first a-LoDBR)) " "
(db-first (first a-LoDBR)) " "
(db-middle (first a-LoDBR)) " "
(db-last (first a-LoDBR)) "\n"
(number->string (db-no (first a-LoDBR))) " "
(db-street (first a-LoDBR)) "\n"
(db-city (first a-LoDBR)) ", "
(db-state (first a-LoDBR)) " "
(number->string (db-zip (first a-LoDBR))) " "
(number->string (db-priority (first a-LoDBR))))))
(cond
[(empty? (rest a-LoDBR))
(cons the-string empty)]
[else
(cons the-string (convert-DBRs (rest a-LoDBR)))]))))
|
Look at this definition for truncate.
;; truncate : [Listof String] -> [Listof String]
;; truncates all lines in LOL so that each is at most 80 characters wide.
(define (truncate LOL)
(cond
[(empty? LOL) empty]
[(> (string-length (first LOL)) 80) (cons (substring (first LOL) 0 81) (truncate (rest LOL)))]
[else (cons (first LOL) (truncate (rest LOL)))]
)
)
|
(hint:)
11111111112222222222333333333344444444445555555555666666666677777777778
12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
The formatting here is ironic, given the function's purpose statement.
Of course, truncate wasn't designed in a vacuum.
It has tests as well:
(equal? (truncate (cons "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
(cons "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" empty)))
(cons "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
(cons "01234567890123456789012345678901234567890123456789012345678901234567890123456789" empty)))
(equal? (truncate (cons "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
(cons "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" empty)))
(cons "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
(cons "01234567890123456789012345678901234567890123456789012345678901234567890123456789" empty)))
|
truncate's purpose statement? (Can you even tell where the test expression ends and the expected result begins?)string-append, make-string and/or build-list.)
Here is another example of poor choice of line-breaks and indentation.
;;evaluate: Expression -> number or false
;;evaluates the given Expression
;;produces number if Expression is numeric, false elsewise.
(define (evaluate exp)
(cond [(numeric? exp) (cond [(add? exp)
(cond [(or (add? (add-left exp))
(add? (add-right exp))
(mul? (add-left exp))
(mul? (add-right exp)))
(+ (evaluate (add-left exp))
(evaluate (add-right exp)))]
[else (+ (add-right exp)
(add-left exp))])]
[(mul? exp)
(cond [(or (add? (mul-left exp))
(add? (mul-right exp))
(mul? (mul-left exp))
(mul? (mul-right exp)))
(* (evaluate (mul-left exp))
(evaluate (mul-right exp)))]
[else (* (mul-right exp)
(mul-left exp))])]
[else exp])]
[else false]))
|
cond to the far right;
it would become unmanagable.Exercise 1: Practice fixing the line width and line breaks in the following file: lab9.deja3.scm.
truncate.
;; truncate : [Listof String] -> [Listof String]
;; truncates all lines in LOL so that each is at most 80 characters wide.
(define (truncate LOL)
(cond
[(empty? LOL) empty]
[(> (string-length (first LOL)) 80) (cons (substring (first LOL) 0 81) (truncate (rest LOL)))]
[else (cons (first LOL) (truncate (rest LOL)))]
)
)
|
We already discussed problems with width of the lines of this function. The other thing that is wrong is more subtle: the closing parens appear on their own lines.
This is bad style for a parenthesized language like the Student Languages because we don't want to read the parentheses; it is faster for our eyes to rely on the indentation of the code to determine how it is structured.
By putting the parentheses on their own lines, you are making them stand out to the reader; but most of the time, we want to ignore the parentheses!
Exercise 2: Practice fixing the parenthesization issues in the following file: lab9.deja2.scm.
We pointed out in the discussion of truncate that it is
faster for our eyes to use the indentation of the code in determining
the roles of its subexpressions.
(Some programming languages do not use delimiters
like { }, [ ] or ( ) to structure their
expressions; they rely solely on indentation to
determine how expressions are structured.
Whether this is a good idea is debatable.)
Unfortunately, this technique breaks down when code isn't indented properly.
Here is an example of how poor indentation can lead one astray. (Warning: this program only "works" verbatim in the "Pretty Big" language, which is not one of the HtDP languages.)
;; recall : Symbol [Listof Symbol] -> [Listof Symbol]
;; produces a list with every toy in lst except a-toy
(define (recall a-toy lst)
(cond
[(empty? lst)"empty" ]
[(symbol? a-toy) (first lst)
(cond
[ (recall a-toy (rest lst))]
[else (cons (recall toy (rest lst)))])]))
|
cond-clauses are there in the outermost cond expression above; 1, 2, 3, 4, or 5?
cond-clause?
How many sub-expressions are there supposed to be in a cond-clause?
;; subst: Symbol Number SymbolicExp -> SymbolicExp
;; produces an expression where all occurances of V are replaced by N
(define (subst V N exp)
(cond
[(symbol? exp)
(cond
[(symbol=? V exp) N]
[else exp])]
[(number? exp) exp]
[(add? exp)
(make-add (subst V N (add-left exp))
(subst V N (add-right exp)))]
[(mul? exp)
(make-mul (subst V N (mul-left exp))
(subst V N (mul-right exp)))]))
|
(subst V N (mul-right exp))
in the above program; is it being passed
to subst or to make-mul?
Exercise 3: Practice fixing the indentation issues in the following file: lab9.deja5.scm.
The order in which you present things is also important. It can affect how you think about the problem.
Consider the following structure and data definitions from Homework 7.
(define-struct add (left right)) ;; An add is a structure ;; (make-add left right) ;; where left and right are numbers, symbols, sub, mul, or div ;;Examples: (define 1add (make-add 1 1)) (define 2add (make-add 4 'x)) (define 3add (make-add 'x 4)) (define-struct sub (left right)) ;; A sub is a structure ;; (make-sub left right) ;; where left and right are numbers, symbols, add, mul, or div ;;Examples: (define 1sub (make-sub 1 1)) (define 2sub (make-sub 4 'x)) (define 3sub (make-sub 'x 4)) (define-struct mul (left right)) ;; A mul is a structure ;; (make-mul left right) ;; where left and right are numbers, symbols, add, sub, or div ;;Examples: (define 1mul (make-mul 1 1)) (define 2mul (make-mul 4 'x)) (define 3mul (make-mul 'x 4)) (define-struct div (left right)) ;; A div is a structure ;; (make-div left right) ;; where left and right are numbers, symbols, add, sub, or mul (define 1div (make-div 1 1)) (define 2div (make-div 4 'x)) (define 3div (make-div 'x 4)) |
In the above structure and data definitions, the developer followed the pattern: "Structure Def; Data Def; Examples; Repeat as Necessary" That is not always the right model to have in mind when writing programs. In particular, when you have mutual references between Data Definitions, you are almost certainly better off using the following process:
Here is a definition similar to the above, written in a more appropriate style.
(define-struct add (left right)) (define-struct sub (left right)) (define-struct mul (left right)) (define-struct div (left right)) ;; An Expression is one of: ;; - Number ;; - Symbol ;; - (make-add Expression Expression) ;; - (make-mul Expression Expression) ;; - (make-sub Expression Expression) ;; - (make-div Expression Expression) ;; Examples: (define 1add (make-add 1 1)) (define 2sub (make-sub 4 'x)) (define 3mul (make-mul 'x 4)) (define 4div (make-div 'x 'y)) |
Expression, but left out of the first
data definition for add, mul, sub, and div?
In general:
Number, String, Symbol,
Character, Boolean, and [Listof X] for free.
To refer to any other class of data,
you need to have a definition
in your code (such a definition might have come from us, or you might
have developed it yourself).Exercise 4: Practice fixing the organization issues in the following file: lab9.connect-four.scm. Note that the program as-is spills blood when run, due to an incorrect reorganization attempted by a prior manager.
Here are some style rules motivated by the examples above. (Thanks to Richard Cobbe for codifying the majority of them.)
These are style guidelines that we expect every submission to follow. If you don not adhere to these guidelines, the homework server may reject your submission.
local definitions start on the first column.
This includes structure, data, and function definitions.( func a) and (func a ).cond clause gets its own line.local definition should gets its own line. In addition, the
body expression for a local also starts on a new line.cond,cond-clause,Often if code looks bad, it is a symptom of deeper problems in the development process. Consider the following definition:
;; numeric? : Expression -> Boolean
;; returns a boolean determining whether or not ex is numeric
(define (numeric? ex)
(cond
[(number? ex) true]
[(sub? ex) (cond
[(and (number? (sub-right ex)) (number? (sub-left ex))) true]
[(and (number? (sub-right ex)) (not (number? (sub-left ex)))) (numeric? (sub-left ex))]
[(and (not (number? (sub-right ex))) (number? (sub-left ex))) (numeric? (sub-right ex))]
[(and (not (number? (sub-right ex))) (not (number? (sub-left ex)))) (and (numeric? (sub-right ex)) (numeric? (sub-left ex)))])]
[(add? ex) (cond
[(and (number? (add-right ex)) (number? (add-left ex))) true]
[(and (number? (add-right ex)) (not (number? (add-left ex)))) (numeric? (add-left ex))]
[(and (not (number? (add-right ex))) (number? (add-left ex))) (numeric? (add-right ex))]
[(and (not (number? (add-right ex))) (not (number? (add-left ex)))) (and (numeric? (add-right ex)) (numeric? (add-left ex)))])]
[(div? ex) (cond
[(and (number? (div-right ex)) (number? (div-left ex))) true]
[(and (number? (div-right ex)) (not (number? (div-left ex)))) (numeric? (div-left ex))]
[(and (not (number? (div-right ex))) (number? (div-left ex))) (numeric? (div-right ex))]
[(and (not (number? (div-right ex))) (not (number? (div-left ex)))) (and (numeric? (div-right ex)) (numeric? (div-left ex)))])]
[(mul? ex) (cond
[(and (number? (mul-right ex)) (number? (mul-left ex))) true]
[(and (number? (mul-right ex)) (not (number? (mul-left ex)))) (numeric? (mul-left ex))]
[(and (not (number? (mul-right ex))) (number? (mul-left ex))) (numeric? (mul-right ex))]
[(and (not (number? (mul-right ex))) (not (number? (mul-left ex)))) (and (numeric? (mul-right ex)) (numeric? (mul-left ex)))])]
[else false]))
|
numeric? function?
When designing a function, often one can use a pre-existing "abstract list function" (or loop) to do the bulk of the work. (You can find the specification for the important loops on page 313.)
But one must know how to select the proper loop for the job. You need to ask yourself questions about the problem:
[Listof A] -> [Listof B],
(for some A and B),
filter.A a different class of data from B?
In that case, consider using map.build-list.andmap or ormap,
depending on whether you're asking if a predicate holds for every
item in the list, or only if there exists some item for which
it holds.
Note that you may need to combine more than one loop together to get the desired effect.
Once you have selected a loop function, you need to determine
how to invoke it; that is, you need to figure out what kind
of function you need to pass into the loop.
The key to this step is to figure out what X
and Y are in the contract for the loop.
You can determine this
based on the classes of data the loop is consuming and
producing.
For example, if we know that we want a [Listof String]
as output, and we have a [Listof Symbol] as input,
then we can consult page 313 and determine:
build-list has potential, because it produces
a [Listof X]. But it takes a number as input,
not a list. So that's not it.filter takes a list as input and produces a list as
output; much better!
filter is:
filter : (X -> Boolean) [Listof X] -> [Listof X]So to get a
[Listof String] out, we need to let X be String.
filter needs a [Listof String] as input, and all we have is a [Listof Symbol]. So that's not it.quicksort has the same problem as filter; it
only produces [Listof String] when it is given a [Listof String]. So that's not it.map takes a [Listof X] and produces a [Listof Y]. This means that we can let Y be String, and we're still free to choose some other X to suit our needs; excellent!
We need to read the purpose statement for map to tell if it
really is the right fit for our needs. For example, if we wanted the output
list to be double the length of the input list, then map alone
isn't going to be sufficient. Let us assume that map's
specification suits the needs of our problem.
Now the only thing left is to determine what we need to feed to map for it to do its job. The contract for map is:
map : (X -> Y) [Listof X] -> [Listof Y]So when we let
X be Symbol and Y be String, we find that map satisfies the contract:
map : (Symbol -> String) [Listof Symbol] -> [Listof String]We've already got the
[Listof Symbol] ready; so all
we need to do is find (or design) a function that turns Symbols
into Strings in the manner that we desire.
Exercise 5: Determine what class of data each of the following expressions belongs to (assuming the contracts given in the comments are correct).
;; A Nat is a natural number (0, 1, 2, ...) ;; f : Nat -> String (build-list 10 f) ;; : ??? ;; g : Nat -> Boolean (map g (list 1 2 3 4)) ;; : ??? ;; h : Nat -> [Listof Symbol] (build-list 20 h) ;; : ??? ;; i : String -> Boolean (andmap i (list "hello" "world")) ;; : ??? ;; j : String -> Boolean (filter j (list "goodbye" "cruel" "world")) ;; : ??? ;; k : (Number String -> String) (foldr k "what me worry?" (list 1 2)) ;; : ???
Exercise 6:
Determine what class of data each of
question-marked contracts (i.e. the bits labelled ???)
must describe in order for
the associated loop expression to belong to the class
of data ascribed to it.
;; z : ??? (quicksort (list 1 2 3) z) ;; : [Listof Number] ;; y : ??? ;; x : Number -> String (filter y (map x (list 10 9 8 7))) ;; : [Listof String] ;; w : ??? ;; v : Symbol -> Number (map w (build-list (v 'x) add1)) ;; : [Listof Symbol] ;; t : ??? ;; s : ??? (ormap t (filter s (list "it's" "me" "mom"))) ;; : Boolean
Exercise 7: (recall recall)
Using an appropriate loop,
design the function recall
to eliminate specific toys from a list.
The function consumes the name of a toy, called ty,
and a list of names, called lon,
and produces a list of names that contains all components of lon
with the exception of ty. For example,
(recall 'robot (cons 'robot (cons 'doll (cons 'dress empty)))) ;; expected value: (cons 'doll (cons 'dress empty))
Exercise 8: (a substitute for substitute)
Using an appropriate loop,
design the function substitute.
The new function consumes two symbols, called new
and old, and a list of symbols.
It produces a new list of symbols by substituting all occurrences
of old by new. For example,
(substitute 'Barbie 'doll (cons 'robot (cons 'doll (cons 'dress empty)))) ;; expected value: (cons 'robot (cons 'Barbie (cons 'dress empty)))
Exercise 9: (summarizing our knowledge with sum-num)
Using an appropriate loop,
design the function sum-num
which takes a [Listof Number] and
produces the sum of all the numbers in the list.
For example,
(equal? (sum-num (list 1 2 3 4)) 10)
Exercise 10: (sum-str with knowledge our summarizing)
Using an appropriate loop,
design the function sum-str
which takes a [Listof String] and
produces a single string that is the result
of appending all of the strings together in reverse order.
For example,
(equal? (sum-str (list "Harry" "Potter" "Ice Cream")) "Ice CreamPotterHarry")