code style
Code Formatting and Style in Scheme
Overview:
Code formatting and style are critical aspects of writing readable, maintainable, and efficient code. In Scheme, and other Lisp-family languages like Scamper and Racket, the coding style emphasizes consistent indentation, meaningful naming, and logical decomposition of problems. This guide outlines best practices for writing clear and structured Scheme code.
Key Elements of Code Style:
- Layout:
- Indent to show nesting: Indentation is used to show the relationships between different parts of the code. For instance:
(define example (lambda (n) (if (< n 0) 0 (* n 2))))
- Avoid long lines: Lines longer than 100 characters should be broken up for readability. Breaking expressions into multiple lines makes it easier to follow complex code.
- Separate top-level statements with blank lines: Leave a blank line between different top-level definitions (e.g.,
define
statements) to make the structure of the program clearer. - Do not place right parentheses on separate lines: Keep closing parentheses on the same line as their last expression to save vertical space.
- Indent to show nesting: Indentation is used to show the relationships between different parts of the code. For instance:
Standard Layouts for Constructs:
-
define
:- Place the body of the
define
on a new line if it’s not a short definition. - Example:
(define origin (pair 0 0))
- Place the body of the
-
lambda
:- Always place the body of a
lambda
on a new line, indented one level. - Example:
(define normalize-name (lambda (name) (string-downcase name)))
- Always place the body of a
-
Function Application:
- For short expressions, keep everything on one line:
(+ 1 1)
- For longer applications, place each argument on a new line:
(map (lambda (n) (* n 2)) (range 0 10))
- For short expressions, keep everything on one line:
-
if
,cond
, andlet
:- Align the branches of an
if
orcond
statement. - For
let
andlet*
, indent the bindings consistently and align the sub-expressions. - Example of
cond
:(cond [(> x y) "gt"] [(< x y) "lt"] [else "eq"])
- Align the branches of an
Naming Conventions:
-
Descriptive Names: Use names that describe the purpose or type of the variable. For example:
- Use
lst
for lists,str
for strings, or descriptive names likeexam-score
. - Predicates (functions that return boolean values) should end with a
?
, such ashungry?
orodd?
. - Functions that convert between types should use the format
from->to
, likeinteger->string
.
- Use
-
Short but Descriptive: Names should be concise but descriptive enough to convey their meaning. Avoid overly verbose names as they can make the code harder to read.
Comments:
- Top-level comments: Every top-level function should include a comment describing its purpose, preconditions (what the function expects), and postconditions (what it guarantees).
- Inline comments: Use inline comments to explain non-obvious sections of code or to give rationale for non-standard decisions.
- Avoid unnecessary comments: Good code structure and naming should reduce the need for excessive comments.
Design Considerations:
-
Decompose problems: Break larger problems into smaller, well-defined subproblems. Use helper functions to avoid redundancy.
-
Use
let
andlet*
to name local computations: Even if the computation is not repeated, usinglet
gives meaningful names to intermediate steps.- Example:
(let* ([x 10] [y (+ x 10)] [z (* y 10)]) (+ x y z))
- Example:
-
Boolean Zen: Use boolean values directly in conditionals instead of writing unnecessary
if
statements. For example: -
Favor existing tools over lambdas: Use
section
orcompose
instead of manually defining a lambda when possible. Additionally, use the|>
(pipeline) function to compose functions in a chain.
Key Reminders:
- Indentation is key: Consistent and correct indentation greatly improves readability.
- Short and meaningful names: Keep variable and function names clear and descriptive.
- Minimize comments: Code should speak for itself with good structure and naming. Only comment where absolutely necessary.
- Decompose and reuse: Decompose problems into small, reusable functions to reduce redundancy and improve code organization.
Self-Check Example:
Poorly formatted function:
;;; (q a t f) -> string?
;;; a : string? the player's name
;;; t : number? the player's first score
;;; f : number? the player's second score
;;; Returns a string that contains a report of the player's
;;; performance in the game.
(define q
(lambda (a t f)
(cond [(< (+ t f) 100) (string-append a " did poorly")])
[(= (+ t f) 100) (string-append a " is doing ok")] [else (string-append
a " is great!")])))
#### Reformatted version:
```scheme
;;; (q a t f) -> string?
;;; a : string? the player's name
;;; t : number? the player's first score
;;; f : number? the player's second score
;;; Returns a string that contains a report of the player's
;;; performance in the game.
(define q
(lambda (a t f)
(cond [(< (+ t f) 100) (string-append a " did poorly")]
[(= (+ t f) 100) (string-append a " is doing ok")]
[else (string-append a " is great!")])))
---
These guidelines ensure your Scheme code is clean, readable, and maintainable, following established conventions for the language.