Add performance deep cuts and parameterize guidance

Performance:
- Beware list-ref in a loop (O(n²) trap)
- Consolidated performance section with data structure selection rationale
- for/fold for single-pass result building

Parameters and dynamic scope:
- Good uses: ports, logging, config, test fixtures
- Bad uses: hidden global state, implicit argument passing
- Document when functions read from parameters

Also simplified Continuations section (parameterize now has its own section).
This commit is contained in:
Dhanji R. Prasanna
2026-01-15 07:47:56 +05:30
parent 52cd19a015
commit 616e0898c7

View File

@@ -43,6 +43,13 @@ Prefer **obvious, readable Racket** over cleverness.
- **Boxes** (`box`, `unbox`, `set-box!`): use for single mutable cells, rarely needed. - **Boxes** (`box`, `unbox`, `set-box!`): use for single mutable cells, rarely needed.
- Don't mix: if a data structure is mutable, keep it internal; expose immutable views. - Don't mix: if a data structure is mutable, keep it internal; expose immutable views.
## Performance
- Use `in-list`, `in-vector`, `in-hash` explicitly in `for` loops — faster than generic sequence.
- **Beware `list-ref` in a loop** — it's O(n) per call, so O(n²) overall. Use vectors for indexed access.
- Don't repeatedly `append` in loops; use `for/list` or accumulate with `cons` then `reverse`.
- Prefer vectors for indexed access, hashes for keyed lookup, lists for sequential iteration.
- Use `for/fold` to build results in one pass instead of multiple traversals.
## Module hygiene ## Module hygiene
```racket ```racket
;; Good: explicit contract-out, interface at top ;; Good: explicit contract-out, interface at top
@@ -60,6 +67,13 @@ Prefer **obvious, readable Racket** over cleverness.
- Use explicit `provide` lists only — never `(all-defined-out)` in production. - Use explicit `provide` lists only — never `(all-defined-out)` in production.
- Use `racket/base` for libraries (faster loading); `racket` for scripts. - Use `racket/base` for libraries (faster loading); `racket` for scripts.
## Parameters and dynamic scope
- **Good uses**: current ports, logging context, configuration, test fixtures.
- **Bad uses**: hidden global state that affects correctness, implicit arguments to avoid passing data.
- Keep `parameterize` scope tight — wrap the smallest expression that needs it.
- Document when a function reads from a parameter (it's implicit input).
- Prefer explicit arguments over parameters when the caller should always think about the value.
## Contracts: when and how much ## Contracts: when and how much
- **Module boundaries**: use `contract-out` for public APIs — catches bugs at the boundary with clear blame. - **Module boundaries**: use `contract-out` for public APIs — catches bugs at the boundary with clear blame.
- **Internal functions**: use `define/contract` sparingly for tricky invariants or during debugging. - **Internal functions**: use `define/contract` sparingly for tricky invariants or during debugging.
@@ -94,10 +108,9 @@ Prefer **obvious, readable Racket** over cleverness.
- Understand `for-syntax` vs runtime; don't accidentally pull runtime values into macros. - 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. - Use `begin-for-syntax` sparingly; prefer `syntax-local-value` patterns when possible.
## Continuations: use sparingly ## Continuations
- Prefer `call/ec` (escape continuations) over full `call/cc` when possible. - Prefer `call/ec` (escape continuations) over full `call/cc` — simpler, faster, sufficient for early exit.
- Use `parameterize` for dynamic scope, not continuation tricks. - Don't use continuations for what `parameterize` or exceptions handle better.
- If using `parameterize`, keep scope tight and document effects.
## Concurrency ## Concurrency
- Use `place`s for CPU parallelism, `thread`s for I/O concurrency. - Use `place`s for CPU parallelism, `thread`s for I/O concurrency.