Files
g3/prompts/langs/carmack.racket.md
Dhanji R. Prasanna e222b9affc Add non-obvious Racket style guide recommendations
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).
2026-01-15 13:49:29 +05:30

101 lines
4.2 KiB
Markdown

Prefer **obvious, readable Racket** over cleverness.
## Keep control flow clean
```racket
;; 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-define` for destructuring.
- Use `for/*` loops and sequences instead of manual recursion unless recursion is clearer.
- Use `cond`, `case`, `and`, `or` cleanly; avoid nested `if` pyramids.
- Prefer `define` over `let`/`let*` when it reduces indentation.
- Use immutable data by default; reach for mutation only when it materially improves clarity/perf.
## Modules: explicit exports
```racket
;; 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 `provide` lists.
- Put `provide` before `require` — interface at top.
- Prefer `contract-out` when exporting public APIs.
- Avoid `provide (all-defined-out)` except in quick prototypes/tests.
- Prefer `require` with explicit identifiers; avoid huge wildcard imports.
- Use `racket/base` for libraries (faster loading); use `racket` for scripts.
## Naming
- Prefix functions with data type of main argument: `board-free-spaces` not `free-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 `struct` variants + `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`, or `error` with a clear message.
- Wrap IO and parsing with `with-handlers` and 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-file` idiomatically.
## Performance
- Prefer vectors for hot loops / indexed access; lists for iteration; hashes for keyed lookup.
- Use `for/fold` or `for/hash` to build results efficiently.
- Use `in-list`, `in-vector`, etc. explicitly in `for` loops for better performance.
- Avoid repeated `append` in 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 raw `syntax-case`) and include good error messages.
- Keep macro output readable and debuggable.
## Phase separation
- Understand `for-syntax` vs runtime; don't accidentally pull runtime values into macros.
- Use `begin-for-syntax` sparingly; prefer `syntax-local-value` patterns when possible.
## Continuations: use sparingly
- Prefer `call/ec` (escape continuations) over full `call/cc` when possible.
- Use `parameterize` for dynamic scope, not continuation tricks.
- If using `parameterize`, keep scope tight and document effects.
## Concurrency
- Use `place`s for CPU parallelism, `thread`s for I/O concurrency.
- Prefer channels (`make-channel`, `channel-put`, `channel-get`) over shared state.
- Use `sync` and events for composable waiting.
## Gotchas
- `eq?` vs `equal?` vs `eqv?`: use `equal?` by default for structural comparison.
- `null?` only works on proper lists; use `empty?` from `racket/list` for generics.
- `string=?` not `equal?` for string comparison in hot paths.
## Testing
- Add `rackunit` tests for tricky logic; prefer table-driven tests.
- Use `module+ test` submodules; run with `raco 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 fmt` style; keep formatting consistent.
- Use `raco pkg install --auto` for dependency resolution.
- Prefer `info.rkt` for package metadata over ad-hoc scripts.