Improve carmack.racket.md with code examples and Racket-specific guidance

Changes:
- Add concrete code examples for match/cond and contract-out
- Add Phase separation section (for-syntax vs runtime)
- Add Continuations section (call/ec over call/cc, parameterize)
- Add Concurrency section (places, threads, channels, sync)
- Add Gotchas section (eq?/equal?/eqv?, null?/empty?, string=?)
- Tighten Packages/tooling (raco pkg install --auto, info.rkt)

Removed generic advice:
- 'Don't swallow exceptions' (obvious)
- 'Add docstrings/comments' (obvious)
- 'Include runnable examples' (obvious)
- 'Optimize the bottleneck only' (obvious)
- Entire 'Output expectations' section (meta, not Racket-specific)
- Removed oddly specific 'file/sha1, file-watch' reference
This commit is contained in:
Dhanji R. Prasanna
2026-01-15 07:20:42 +05:30
parent 65807eea99
commit 5ad9fb3718

View File

@@ -1,60 +1,88 @@
RACKET-SPECIFIC GUIDANCE (apply by default) Prefer **obvious, readable Racket** over cleverness.
- Prefer idiomatic Racket: ## Keep control flow clean
- Use `match` / `match-define` for destructuring. ```racket
- Use `for/*` loops and sequences instead of manual recursion unless recursion is clearer. ;; Good: match for destructuring
- Use `cond`, `case`, `and`, `or` cleanly; avoid nested `if` pyramids. (match-define (list name age) (get-user-info id))
- Use immutable data by default; reach for mutation only when it materially improves clarity/perf.
- Modules and structure: ;; Good: cond over nested if
- Organize code into small modules with explicit `provide` lists (prefer `provide (contract-out ...)` when exporting). (cond
- Avoid `provide (all-defined-out)` except in quick prototypes/tests. [(empty? items) '()]
- Prefer `require` with explicit identifiers; avoid huge wildcard imports. [(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.
- Use immutable data by default; reach for mutation only when it materially improves clarity/perf.
- Contracts and types: ## Modules: explicit exports
- If in untyped Racket: add contracts at module boundaries for public APIs (`contract-out`), especially for callbacks and data shapes. ```racket
- If the project uses `typed/racket`, keep typed/untyped boundaries clean and document them. ;; Good: explicit contract-out
- Use predicates + struct definitions to make data models explicit. (provide
(contract-out
[process-data (-> input/c output/c)]))
- Data modeling: ;; Bad: leaky exports
- Prefer `struct` (possibly `#:transparent`) for domain objects, not ad-hoc hash soup. (provide (all-defined-out))
- For enums/variants: consider `struct` variants + `match`, or symbols with clear validation. ```
- For “records loaded from YAML/JSON”: validate once at the boundary; keep internal representation consistent. - Organize code into small modules with explicit `provide` lists.
- 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.
- Error handling: ## Contracts and types
- Use `raise-argument-error`, `raise-user-error`, or `error` with a clear message. - If in untyped Racket: add contracts at module boundaries for public APIs, especially for callbacks and data shapes.
- Wrap IO and parsing with `with-handlers` and rethrow with context (what file, what phase). - If the project uses `typed/racket`, keep typed/untyped boundaries clean and document them.
- Dont swallow exceptions; surface actionable diagnostics. - Use predicates + struct definitions to make data models explicit.
- IO, paths, and portability: ## Data modeling
- Use `build-path`, `simplify-path`, `path->string` as needed; dont concatenate path strings manually. - Prefer `struct` (possibly `#:transparent`) for domain objects, not ad-hoc hash soup.
- Use `call-with-input-file` / `call-with-output-file` and ports idiomatically. - For enums/variants: consider `struct` variants + `match`, or symbols with clear validation.
- Prefer `file/sha1`, `file-watch`-style libs (if present) for reload tooling; otherwise design a simple polling fallback. - Validate external data (YAML/JSON) once at the boundary; keep internal representation consistent.
- Performance + allocations: ## Error handling
- Prefer vectors for hot loops / indexed access; lists for iteration; hashes for keyed lookup. - Use `raise-argument-error`, `raise-user-error`, or `error` with a clear message.
- Use `for/fold` or `for/hash` to build results efficiently. - Wrap IO and parsing with `with-handlers` and rethrow with context (what file, what phase).
- Avoid repeated `append` in loops; accumulate then reverse if needed.
- If profiling is needed: use `profile` or `time`, and optimize the bottleneck only.
- Macros and syntax: ## IO and paths
- Dont write macros unless it meaningfully reduces boilerplate or enforces invariants. - Use `build-path`, `simplify-path`, `path->string`; don't concatenate path strings manually.
- If writing macros: use `syntax-parse` (not raw `syntax-case`) and include good error messages. - Use `call-with-input-file` / `call-with-output-file` idiomatically.
- Keep macro output readable and debuggable.
- Testing + docs: ## Performance
- Add `rackunit` tests for tricky logic; prefer table-driven tests. - Prefer vectors for hot loops / indexed access; lists for iteration; hashes for keyed lookup.
- When writing public APIs, add docstrings/comments; if theres a lib boundary, consider Scribble docs. - Use `for/fold` or `for/hash` to build results efficiently.
- Include runnable examples in comments when it helps. - Avoid repeated `append` in loops; accumulate then reverse if needed.
- Concurrency/events (common in engines/tools): ## Macros: use sparingly
- Prefer clear event loops and message passing; avoid shared mutable state unless protected. - Don't write macros unless it meaningfully reduces boilerplate or enforces invariants.
- If using parameters (`parameterize`), keep scope tight and document effects. - If writing macros: use `syntax-parse` (not raw `syntax-case`) and include good error messages.
- Keep macro output readable and debuggable.
- Packages/tooling: ## Phase separation
- Assume `raco fmt` / `racket-format` style; keep formatting consistent. - Understand `for-syntax` vs runtime; don't accidentally pull runtime values into macros.
- If suggesting deps, name the package and `raco pkg install` usage. - Use `begin-for-syntax` sparingly; prefer `syntax-local-value` patterns when possible.
- Output expectations: ## Continuations: use sparingly
- When proposing code changes, include: new/changed function signatures, required `require`s, and small usage examples. - Prefer `call/ec` (escape continuations) over full `call/cc` when possible.
- If unsure about a librarys availability, provide a fallback approach that uses base Racket. - 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.
- Consider Scribble docs for library boundaries.
## 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.