From docs.racket-lang.org/style, added only the non-obvious tips: - Prefer define over let/let* (reduces indentation) - Put provide before require (interface at top) - Use racket/base for libraries (faster loading) - Naming: prefix functions with data type (board-free-spaces) - Use in-list/in-vector explicitly in for loops (performance) - Use module+ test submodules with raco test - Size limits: ~500 lines/module, ~66 lines/function Skipped basic conventions LLMs already know (predicate suffixes, etc).
4.2 KiB
4.2 KiB
Prefer obvious, readable Racket over cleverness.
Keep control flow clean
;; Good: match for destructuring
(match-define (list name age) (get-user-info id))
;; Good: cond over nested if
(cond
[(empty? items) '()]
[(special? (first items)) (handle-special items)]
[else (process-normal items)])
- Use
match/match-definefor destructuring. - Use
for/*loops and sequences instead of manual recursion unless recursion is clearer. - Use
cond,case,and,orcleanly; avoid nestedifpyramids. - Prefer
defineoverlet/let*when it reduces indentation. - Use immutable data by default; reach for mutation only when it materially improves clarity/perf.
Modules: explicit exports
;; Good: explicit contract-out
(provide
(contract-out
[process-data (-> input/c output/c)]))
;; Bad: leaky exports
(provide (all-defined-out))
- Organize code into small modules with explicit
providelists. - Put
providebeforerequire— interface at top. - Prefer
contract-outwhen exporting public APIs. - Avoid
provide (all-defined-out)except in quick prototypes/tests. - Prefer
requirewith explicit identifiers; avoid huge wildcard imports. - Use
racket/basefor libraries (faster loading); useracketfor scripts.
Naming
- Prefix functions with data type of main argument:
board-free-spacesnotfree-spaces.
Contracts and types
- If in untyped Racket: add contracts at module boundaries for public APIs, especially for callbacks and data shapes.
- If the project uses
typed/racket, keep typed/untyped boundaries clean and document them. - Use predicates + struct definitions to make data models explicit.
Data modeling
- Prefer
struct(possibly#:transparent) for domain objects, not ad-hoc hash soup. - For enums/variants: consider
structvariants +match, or symbols with clear validation. - Validate external data (YAML/JSON) once at the boundary; keep internal representation consistent.
Error handling
- Use
raise-argument-error,raise-user-error, orerrorwith a clear message. - Wrap IO and parsing with
with-handlersand rethrow with context (what file, what phase).
IO and paths
- Use
build-path,simplify-path,path->string; don't concatenate path strings manually. - Use
call-with-input-file/call-with-output-fileidiomatically.
Performance
- Prefer vectors for hot loops / indexed access; lists for iteration; hashes for keyed lookup.
- Use
for/foldorfor/hashto build results efficiently. - Use
in-list,in-vector, etc. explicitly inforloops for better performance. - Avoid repeated
appendin loops; accumulate then reverse if needed.
Macros: use sparingly
- Don't write macros unless it meaningfully reduces boilerplate or enforces invariants.
- If writing macros: use
syntax-parse(not rawsyntax-case) and include good error messages. - Keep macro output readable and debuggable.
Phase separation
- Understand
for-syntaxvs runtime; don't accidentally pull runtime values into macros. - Use
begin-for-syntaxsparingly; prefersyntax-local-valuepatterns when possible.
Continuations: use sparingly
- Prefer
call/ec(escape continuations) over fullcall/ccwhen possible. - Use
parameterizefor dynamic scope, not continuation tricks. - If using
parameterize, keep scope tight and document effects.
Concurrency
- Use
places for CPU parallelism,threads for I/O concurrency. - Prefer channels (
make-channel,channel-put,channel-get) over shared state. - Use
syncand events for composable waiting.
Gotchas
eq?vsequal?vseqv?: useequal?by default for structural comparison.null?only works on proper lists; useempty?fromracket/listfor generics.string=?notequal?for string comparison in hot paths.
Testing
- Add
rackunittests for tricky logic; prefer table-driven tests. - Use
module+ testsubmodules; run withraco test. - Consider Scribble docs for library boundaries.
Size limits
- ~500 lines per module (1000 tolerable, 10000 is a god-file).
- ~66 lines per function (one screen).
Packages/tooling
- Assume
raco fmtstyle; keep formatting consistent. - Use
raco pkg install --autofor dependency resolution. - Prefer
info.rktfor package metadata over ad-hoc scripts.