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 and readability.
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. Think of the second semicolon as making an emphatic point.
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.
The screenshots below illustrate the use of #; and how DrRacket and Emacs (Racket mode) color such comments by default.
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.
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 begin.
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.
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 accelerate reading.
Of course, many constructs (e.g. call-with-values) or higher-order functions (e.g. 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.
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 will do.
When you handle exceptions, specify the exception as precisely as possible.
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 programmers.
Avoid plural when naming collections and libraries. Use racket/contract and data/heap, not racket/contracts or data/heaps.