When we write in natural language, we employ a variety of conventions to help ensure that our audience can more easily read what we write. We put spaces between words. We put spaces between sentences. (These spaces are supposed to be larger in typeset text. In the days of typewriters, we used two spaces between sentences.) We put blank lines between paragraphs or indent the first line of a paragraph. We indent long pieces of quoted text.
Following conventions in programming languages also helps our readers. In some cases, failing to follow conventions makes our code significantly harder to read. You will find that different programmers and different programming teams have different conventions, just as different teachers have different expectations for how you separate paragraphs.
Here are some conventions we generally expect you to follow in writing Racket. These follow the biases of SamR. You may also find that some involve things you have not yet learned in Racket. Feel free to ignore those.
Fortunately, DrRacket will do this for you if you hit Ctrl-I. Properly indented nested code makes it much easier to follow the logical flow.
Write
(fun a b c)
or
(fun a
b
c)
Doing so makes it easier for your reader to find all of the parameters.
Rarely should you use any other form.
For example, make sure to include the spaces in the following.
(+ (exp) (exp) (exp))
Once again, doing so makes it easier for your reader to find all of the parameters. To many readers, it’s as natural to expect to see parameters separated by spaces in code as it is to see words separated by spaces in text.
Write
(fun a
b
c)
which is much easier to read than
(fun
a
b
c)
That’s a long-standing custom. Programmers in other languages, like C, find it odd because they make it a regular practice to have right braces on lines by themselves to check nesting. But Scheme programmers like to save vertical space. The right parens by themselves also convey little meaning.
We do, on occasion, include right parens on separate lines in code templates we give you to finish. Please move those when you are finished.
If there’s a lot of text on one line, the reader will have to spend time trying to pull the line apart. By separating things, you make it easier for the reader to find the parts of the expression. I generally try to ensure that no text goes beyond the 80th column. I also try to use no more than six or so “words” on a line.
define
and lambda
, put the name on one line, the lambda
and parameters on the next line, and the body after that.(define NAME
(lambda (PARAMS)
BODY))
There are a few reasons we use this strategy. First, it ensures that the body is not indented very far. That’s particularly important when we are dealing with larger or more complex bodies. Second it makes it easier to find the parts. Uniform formatting means that we can easily find the parameter names and the procedure names.
if
; place the consequent and alternate on separate lines(if GUARD
CONSEQUENT
ALTERNATE)
As in the case of procedures above, it makes it easy for us to find the three parts. In conjunction with DrRacket’s auto-indent feature, it also helps us identify incorrectly parenthesized code.
cond
statements on separate lines(cond
[GUARD1
CONSEQUENT1a
CONSEQUENT1b
...]
[GUARD2
CONSEQUENT2a
CONSEQUENT2b
...]
...
[else
ALTERNATEa
ALTERNATEb
...])
By now, you can guess why. This makes it easier to find the guards and the consequents and it also make it easier to find errors by re-indenting.
cond
on a line by itselfSee the example above. We mostly do that to ensure that the body of the conditional is not indented too far.
match
, follow similar conventions to cond
That is, a typical match
expression should look something like the
following.
(match EXP
[PATTERN1
CONSEQUENT1a
CONSEQUENT1b
...]
[PATTERN2
CONSEQUENT2a
CONSEQUENT2b
...]
[default
...])
You don’t really need the default
. The Grinnell CS faculty are mixed
on whether or not it’s necessary. But if you don’t include a default,
you’ll get a matching error. (Because default
is a variable name, it
matches anything.)
We place the documentation before the procedure because that’s the part that most people want to read first (or the only part people want to read at all). They care what your procedure does (or is supposed to do).
;;; (proc PARAM1 PARAM2 ... PARAMn) -> TYPE?
;;; PARAM1 : TYPE?
;;; PARAM2 : TYPE?
;;; ...
;;; PARAMn : TYPE?
;;; EXPLANATION
(define proc
(lambda (PARAM1 PARAM2 ... PARAMn)
BODY))
We place blank lines between procedures for the same reason that we often place blank lines between paragraphs in an essay; it makes it easier to tell where one procedure ends and the next begins.
Just as you would not want extra spaces in the middle of a sentence, please don’t put them in the middle of your code. And just as you would not put spaces after a parenthetical ( like this one ) , please don’t put them after your open parens or before your close parens.
We use tick marks to indicate symbols and certain lists. Remember that the tick mark represents “take this verbatim”. When you put a tick mark within something that already has a tick mark, you confuse DrRacket. (Well, you don’t really confuse DrRacket, but you create a structure that will confuse you.)
You’ll note that DrRacket only uses one tick mark when it returns a nested list.
> (list (list 1 2 3) null (list 'a 'b))
'((1 2 3) () (a b))
Even though 'a
and 'b
are symbols, DrRacket doesn’t put tick marks
immediately before them. Even though (1 2 3)
is not intended as
a procedure call, DrRacket doesn’t put a tick mark immediately before
it. That outermost tick mark handles everything.
Like DrRacket, you should use only one tick mark per expression.
When you sit down with a problem, it’s always tempting to start writing code. But writing code should be your last step. If you think of the problem in terms of the procedure you will write to solve it, there are a few steps you should take before writing code.
First, you should make sure you understand the goal of the procedure and what the inputs and outputs will look like. The best way to do that is to write documentation. When you document a procedure, you clarify the procedure for yourself. You also clarify it for anyone who might be helping you develop it.
You gain further understanding of what you want the procedure to do by writing tests for the procedure. What should it do on this input? How about this one? What are cases that might cause problems?
As you’ve learned by now, having a big picture view of the approach you will use to implement a procedure helps you write more correct code the first time. So a high-level design helps.
Only then should you begin to implement the procedure.
You may note that more experienced programmers (e.g., your faculty) seem to be skipping some of these steps. That’s because they are doing much of that work in their head. That is, they’ve thought about input and output types, about edge cases and normal cases, about how they might structure their code. You just don’t see all that work. But if you ask them, they’ll likely be able to talk about all of those issues.
The name of a procedure, parameter, or variable should tell the reader
something about the procedure, parameter, or variable. Sometimes it
describes the type, as in when we name a parameter str
or lst
.
Sometimes it describes the purpose, as in sum-of-numbers
. But
it should convey something meaningful.
It should also convey the right meaning. Don’t use str
to name
a list or lst
to name an integer. You’ve added to your cognitive
load, made the world feel to your reader like the evil color test.
There are many problems with repeated code. Repeated code often causes the computer to do extra work, which slows down your program. Repeated code is also harder to change; if you realize that the code you repeated needs to change, you need to make the change in multiple places.
How do you avoid repeated code? You’ve seen at least two approaches.
You can use a let
(or let*
) to name a value you are using multiple
times. You can also write a procedure that encapsulates the key ideas.
let
bindings are more likely to help you avoid work and should be
used for identical expressions. Procedures can be used when you want
to look at bigger-picture issues.
Forthcoming.
Forthcoming.