4 Choosing the Right Construct
Racket provides a range of constructs for the same or similar purposes.
Although the Racket designers don’t think that there is one right way for
everything, we prefer certain constructs in certain situations for consistency
Following Lisp and Scheme tradition, we use a single semicolon for in-line
comments (to the end of a line) and two semicolons for comments that start
a line. This request does not contradict the programs in this
document. They use two semicolons for full-line comments in source but
scribble renders only one. Think of the second semicolon as making an
Seasoned Schemers, not necessarily Racketeers, also use triple and
quadruple semicolons. This is considered a courtesy to distinguish file
headers from section headers.
In addition to ;, we have two other mechanisms for commenting code:
#|...|# for blocks and #; to comment out an expression.
Block comments are for those rare cases when an entire block of
definitions and/or expressions must be commented out at once.
Expression comments—#;—apply to the following
S-expression. This makes them a useful tool for debugging. They can even
be composed in interesting ways with other comments, for example, #;#;
will comment two expressions, and a line with just ;#; gives you a
single-character “toggle” for the expression that starts on the next
line. But on the flip side, many tools don’t process them
properly—treating them instead as a # followed by a commented line.
For example, in DrRacket S-expression comments are ignored when it comes
to syntax coloring, which makes it easy to miss them. In Emacs, the
commented text is colored like a comment and treated as text, which makes
it difficult to edit as code. The bottom line here is that #;
comments are useful for debugging, but try to avoid leaving them in
committed code. If you really want to use #;, clarify their use with
a line comment (;).
Racket comes with quite a few definitional constructs, including
let, let*, letrec, and define. Except
for the last one, definitional constructs increase the indentation level.
Therefore, favor define when feasible.
binding block is not easily replaced with a
series of define
s because the former has sequential
and the latter has mutually recursive
Like definitional constructs, conditionals come in many flavors,
too. Because cond and its relatives (case,
match, etc) now allow local uses of define, you should
prefer them over if.
Also, use cond instead of if to eliminate explicit
The above “good” example would be even better with match. In
general, use match to destructure complex pieces of data.
You should also favor cond (and its relatives) over if to
match the shape of the data definition. In particular, the above examples
could be formulated with and and or but doing so would
not bring across the recursion as nicely.
Don’t nest expressions too deeply. Instead name intermediate results. With
well-chosen names your expression becomes easy to read.
Clearly “too deeply” is subjective. On occasion it also isn’t the
nesting that makes the expression unreadable but the sheer number of
subexpressions. Consider using local definitions for this case, too.
4.5 Structs vs Lists
Use structs when you represent a combination of a small and fixed
number of values. For fixed length (long) lists, add a comment or even a
contract that states the constraints.
If a function returns several results via values, consider using
structs or lists when you are dealing with four or more values.
4.6 Lambda vs Define
While nobody denies that lambda is cute, defined
functions have names that tell you what they compute and that help
Even a curried function does not need lambda
The left side signals currying in the very first line of the function,
while the reader must read two lines for the version on the right side.
Of course, many constructs (call-with ..) or higher-order functions
(filter) are made for short lambda; don’t hesitate to use
lambda for such cases.
4.7 Identity Functions
The identity function is values:
With the availability of for/fold, for/list,
for/vector, and friends, programming with for loops
has become just as functional as programming with map and
foldr. With for* loops, filter, and termination clauses
in the iteration specification, these loops are also far more concise than
explicit traversal combinators. And with for loops, you can
decouple the traversal from lists.
In this example, the for
loop on the left comes with two
advantages. First, a reader doesn’t need to absorb an intermediate
. Second, the for
loop naturally generalizes to
other kinds of sequences. Naturally, the trade-off here is a loss of
efficiency; using in-list
to restrict the good
the same range of data as the bad
one speeds up the former.
Note: for traversals of user-defined sequences tend to be
slow. If performance matters in these cases, you may wish to fall back on
your own traversal functions.
4.9 Functions vs Macros
Define functions when possible, Or, do not introduce macros when functions
A function is immediately useful in a higher-order context. For a macro,
achieving the same goal takes a lot more work.
When you handle exceptions, specify the exception as precisely as
Using (lambda _ #t)
as an exception predicate suggests to the
reader that you wish to catch every possible exception, including failure
and break exceptions. Worse, the reader may think that you didn’t remotely
consider what exceptions you should
It is equally bad to use exn?
as the exception predicate even if
you mean to catch all kinds of failures. Doing so catches break
exceptions, too. To catch all failures, use exn:fail?
as shown on
Finally, a handler for a exn:fail?
clause should never
succeed for all possible failures because it silences all kinds of
exceptions that you probably want to see:
If you need to set a parameter, use parameterize:
As the comparison demonstrates, parameterize clearly delimits the
extent of the change, which is an important idea for the reader. In
addition, parameterize ensures that your code is more likely to
work with continuations and threads, an important idea for Racket
Avoid plural when naming collections and libraries. Use racket/contract
and data/heap, not racket/contracts or data/heaps.