(Disclaimer: this page is not intended to be a complete list of things you need to know. See the long FAQ answer for a thorough explanation.)
How can I improve my Racket coding style and efficiency?
- You are given solution files for homework — use them to learn how good programs look like. (If it doesn’t look good, ask about it.)
- Write short functions, where each function provides a single, well-defined operation. Small functions are easier to read, write, test debug, and understand.
- Add tests to your code, make sure that they work. The tests should be added for each logical piece of code from an API point of view. In our case, this means that for the interpreters, there is a single block of tests at the end of the file. (This is different from HtDP, where you test each function in itself, since the functions are unrelated.)
- Use descriptive binding names. If the purpose of an identifier isn’t clear from its name, document it with a descriptive comment. In fact, even if the purpose is evident from the name, it is still worth documenting your code.
- Don’t write C/Java/Python/… code in Racket. Use the appropriate predefined functions — look in the class notes, in the Racket manuals, and other resources. Don’t put a close parenthesis on a line by itself — this can really aggravate programmers who grew up on Lisp or Scheme. If you choose not to use DrRacket to develop your code, use some other Scheme/Lisp oriented editor. You don’t need to stick comments on close parentheses to mark which expression they close.
- DrRacket has several different languages, some have very different
execution semantics, and some are not even close to Scheme. Make sure
you use the correct language. In our case, this means always using the
“determine language from source” option, and using the correct
#lang pl ...
line.
To improve the readability of your code
-
Make sure that your code is properly indented. When in doubt, let DrRacket do it for you (the
Tab
key is your friend), and/or ask on piazza. Your code should be easy to read without counting parens or even looking at them. -
Racket conventions dictate using square brackets instead of round parentheses in some cases to further improve readability. Two common examples are
let
(and similar) binding forms where each id-value pair is square-bracketed (and all are in round parens), andcond
and similar (includingmatch
andcases
) forms where each condition-result pair is square-bracketed. For example:(define (foo x y)
(let ([sum (+ x y)]
[diff (abs (- x y))])
(cond [(< x y) sum]
[(> x y) diff]
[else (- sum diff)])))Never use curly braces in Racket code — we use them exclusively in code that is written in languages we implement.
-
Use white space appropriately. For example,
Good:
(define (foo x y)
(if (< x 0)
(let ([z (+ x y 10)])
(- (* z z) 5))
(* y y)))Bad:
(define (foo x y)(if (< x 0)
(let ([z (+ x y 10)])
(- (* z z) 5))
(* y y)))
(define ( foo x y )
( if ( < x 0 )
(let ([ z (+ x y 10) ])
( - ( * z z ) 5 ))
(* y y )
)
) ; end fooAlthough the Racket reader does not care which of these you use, most experienced Racket programmers find the first example much easier to read than the last two (the first one is not readable without counting parentheses and the second is too sparse to read conveniently).
As a general rule of thumb: do not use white spaces (especially newlines) after an open paren or before a closing one, and always use white space before a group of open-parens and after a group closing parens.
-
It is very rare to use a newline after the first part of a Racket form, even more rare after special form keywords like
define
,if
,let
etc. If you think you need one, ask! (Hint: you most likely don’t.) -
Use newlines to separate semantically distinct code segments, not too many so your code look like scattered unconnected lines too widely, and not too few so it looks like a pile of text.
-
Don’t write lines longer than 79 characters. This makes it more difficult to read when printed out or read on an 80x24 terminal window, but more importantly, wider lines cannot be read conveniently in all contexts (same reason for narrow column widths in newspapers, both traditional and on-line). In fact, it’s better to restrict your code to a line length of 72 characters.
-
Use
cond
instead of nestedif
statements. Be sure to check for unreachable cases, and eliminate those cond-clauses. Remember thatcond
tries each case, so there is no need to check in a certain branch that one of the previous conditions does not hold. -
Usually, you should not use
cond
if there is a single case and then anelse
case —if
is fine for that. -
Avoid using redundant
if
s with literal#f
s and#t
s, these are usually equivalent to some usage ofand
andor
. For example,(if c #t x)
is the same as(or c x)
,(if c x #f)
is the same as(and c x)
, and(if c #f #t)
is the same as(not c)
. Remember that in Racket any value other than#f
is considered true. -
Prefer using
#t
and#f
, which are the literal boolean values, rather thantrue
andfalse
which are bound by the base language to these values. -
When including lists as fixed constants in your code (for example, as values for tests), use the quoted format rather than the
list
function. E.g.'(1 2 c)
instead of(list 1 2 'c)
. Such quoted lists are literal constants, rather than calling thelist
function at runtime to construct the list. Similarly, you should avoid using thecons
function when possible (unless you’recons
ing onto a function call or an identifier that is bound to a list) . For empty lists,null
is a valid literal, but you prefer'()
which is again, the literal empty list value rather than a binding to it. -
Avoid one-use bindings (either
let
s ordefine
s), and prefer using the expression directly instead. There are cases where such bindings are fine — when the expression is not meaningful by itself, or when code becomes nested to a point of becoming unreadable. Such cases are, however, less frequent than what you’d expect. -
You should generally prefer local
define
forms instead oflet
forms when possible, especially for local helper function definitions. -
Use the naming conventions of your language — in our case: use
-
to separate words in an identifier (make-point
, notmake_point
and notmakePoint
),?
as the last character of a predicate (point?
, notisPoint
), and!
as the last character for functions that change some state (mutation). Use lower-case names except for constructors (we capitalize them by convention) and defined type names (we use all-caps by convention). -
The most important point is for you to be consistent in your stylistic choices, even if you have reasons for not following the conventions. But make sure you ask if you want to diverge from these guidelines.
Documentation
-
Comment your code. A common convention is to use one semicolon for a comment that follows code on the same line, and two semicolons for comments that are on a line of their own, indented as the code that is described. (Some people use three semicolons for toplevel comments, but this is less common.) The way to think about this is that the number of semicolons used roughly corresponds to the length of the comment. In addition, you can have a multi-line comment
#|...|#
(for example, write more free-form explanations, or comment out pieces of code). The text of a comment should be separated from the comment delimiters exactly one space.An example that demonstrates all of these conventions:
;;; This function implements the square search loop.
;;; It keeps improving an initial guess until the
;;; value is good enough.
(define (try guess x)
;; If it's a good guess return it, otherwise, improve
(if (good-enough? guess x)
guess
(try (improve guess x) x))) ; loop back
#|
(< 3.99 (try 2 0) 4.01)
|# -
Be sure to write a short and clear purpose statements. Fundies should have you well prepared for this (including good style for these, like the fact that they usually start with a verb).
-
However, phrase it as readable text, no need for template headers like “Given” and “Returns” — make it part of the readable text instead.