249 lines
5.9 KiB
Markdown
249 lines
5.9 KiB
Markdown
# The takeaway
|
||
|
||
Consumed data must block recursion.
|
||
Control data must not drive recursion.
|
||
Branches with work must be lazy.
|
||
Top-level fixed points must be hidden behind wrappers.
|
||
Fixed-format data should be destructured finitely, not sliced recursively.
|
||
|
||
## Rules for normalization-safe `tricu`
|
||
|
||
A top-level definition must normalize when its runtime inputs are still abstract. Therefore, avoid any shape where known control data can unfold recursion before the consumed data is available.
|
||
|
||
## 1. Put consumed data first
|
||
|
||
Recursive workers should take the structure they consume before counters, indexes, limits, accumulators, or other control state.
|
||
|
||
Avoid:
|
||
|
||
```tricu
|
||
worker index records state
|
||
```
|
||
|
||
Prefer:
|
||
|
||
```tricu
|
||
worker records index state
|
||
```
|
||
|
||
The worker’s first real operation should usually be a case split on the consumed value:
|
||
|
||
```tricu
|
||
worker_ = (self records state :
|
||
lazyList
|
||
nilCase
|
||
consCase
|
||
records)
|
||
```
|
||
|
||
## 2. Do not use generic recursive consumers on abstract fixed-format data
|
||
|
||
Avoid applying helpers like these to abstract values in top-level-normalized definitions:
|
||
|
||
```tricu
|
||
take n xs
|
||
drop n xs
|
||
nth n xs
|
||
length xs
|
||
startsWith? prefix xs
|
||
bytesTake n bytes
|
||
bytesDrop n bytes
|
||
```
|
||
|
||
These can be driven by known counters, indexes, lengths, or prefixes while `xs` is still abstract.
|
||
|
||
For fixed-format data, use finite destructuring helpers instead:
|
||
|
||
```tricu
|
||
withNodePayloadForkIndices payload shortK indicesK
|
||
hashShard hash
|
||
```
|
||
|
||
This keeps the recursion bounded by syntax, not by a runtime counter.
|
||
|
||
## 3. Use lazy eliminators when a branch contains work
|
||
|
||
If a branch contains recursion, IO construction, parsing, lookup, response construction, or anything that may recurse internally, do not pass it as an ordinary branch value.
|
||
|
||
Avoid:
|
||
|
||
```tricu
|
||
matchBool
|
||
resultNow
|
||
(self rest state)
|
||
cond
|
||
```
|
||
|
||
Prefer:
|
||
|
||
```tricu
|
||
lazyBool
|
||
(_ : resultNow)
|
||
(_ : self rest state)
|
||
cond
|
||
```
|
||
|
||
Same rule for result, maybe, and list elimination:
|
||
|
||
```tricu
|
||
lazyBool
|
||
lazyResult
|
||
lazyMaybe
|
||
lazyList
|
||
```
|
||
|
||
Strict eliminators are safe only when both branches are already cheap normal forms.
|
||
|
||
## 4. Do not expose top-level fixed points directly
|
||
|
||
Avoid top-level definitions like:
|
||
|
||
```tricu
|
||
foo_ = y (self input state : ...)
|
||
```
|
||
|
||
Prefer the library-style split:
|
||
|
||
```tricu
|
||
foo_ = (self input state : ...)
|
||
|
||
foo = (input state :
|
||
y foo_ input state)
|
||
```
|
||
|
||
This prevents each independently-normalized top-level definition from trying to normalize the fixed point itself.
|
||
|
||
## 5. Keep recursive self-application small and structurally progressing
|
||
|
||
Prefer recursive calls shaped like:
|
||
|
||
```tricu
|
||
self rest nextState
|
||
```
|
||
|
||
over wide calls like:
|
||
|
||
```tricu
|
||
self rest index i limit acc flags
|
||
```
|
||
|
||
Pack non-consumed state into a record/pair if needed.
|
||
|
||
The consumed argument should visibly progress:
|
||
|
||
```tricu
|
||
self rest nextState
|
||
```
|
||
|
||
not restart from the original structure:
|
||
|
||
```tricu
|
||
self originalRecords newIndex newState
|
||
```
|
||
|
||
Restarting from the original input inside recursive branches can create residual trees with no obvious structural progress.
|
||
|
||
## 6. Recursive state updates must be non-recursive
|
||
|
||
Do not call a recursive helper while constructing the next recursive state.
|
||
|
||
Avoid:
|
||
|
||
```tricu
|
||
self rest (listSnoc acc value)
|
||
```
|
||
|
||
because `listSnoc` is itself recursive.
|
||
|
||
Prefer constant-time constructors:
|
||
|
||
```tricu
|
||
self rest (pair value acc)
|
||
```
|
||
|
||
If order matters, reverse later only when the input is concrete, or store explicit indexes in an association list.
|
||
|
||
## 7. Do not rebuild from the whole input when a prefix invariant exists
|
||
|
||
If validation guarantees child references point backward, use that invariant.
|
||
|
||
Avoid:
|
||
|
||
```tricu
|
||
buildTree allRecords childIndex
|
||
```
|
||
|
||
inside the build of each node.
|
||
|
||
Prefer:
|
||
|
||
```tricu
|
||
lookup childIndex builtPrefix
|
||
```
|
||
|
||
For Arboricx nodes, this meant scanning records once left-to-right and resolving children from `builtTrees`.
|
||
|
||
## 8. Make route/path helpers consumed-data-driven
|
||
|
||
For request paths, hashes, and byte strings, avoid counter/prefix-driven recursive operations over abstract request data.
|
||
|
||
Avoid:
|
||
|
||
```tricu
|
||
take 3 hash
|
||
drop 23 target
|
||
startsWith? prefix target
|
||
```
|
||
|
||
Prefer:
|
||
|
||
```tricu
|
||
hashShard hash
|
||
stripPrefix prefix target
|
||
```
|
||
|
||
where the helper case-analyzes the consumed runtime data before recurring.
|
||
|
||
For fixed small slices like the first three hash bytes, use finite destructuring rather than `take`.
|
||
|
||
## 9. Treat top-level normalization as stricter than runtime evaluation
|
||
|
||
A function can be semantically correct at runtime and still fail import normalization.
|
||
|
||
Ask this for every top-level definition:
|
||
|
||
```text
|
||
Can this normalize while all of its arguments are unknown?
|
||
```
|
||
|
||
If the answer depends on “the branch will not be taken” or “the input will be concrete by then,” the definition is probably not normalization-safe.
|
||
|
||
## 10. When a definition hangs alphabetically, inspect reachable dependencies
|
||
|
||
The alphabetically first hanging definition is not necessarily the root cause. It may simply be the first definition that reaches a later problematic helper.
|
||
|
||
Debug by replacing reachable branches with constants:
|
||
|
||
```tricu
|
||
foo = (... : pure notFoundResponse)
|
||
```
|
||
|
||
Then add back one dependency at a time. If a constant version normalizes, the issue is in reachable branch work, not the wrapper itself.
|
||
|
||
## Compact checklist
|
||
|
||
Before adding or exporting a definition, check:
|
||
|
||
```text
|
||
1. Does every recursive worker consume unknown data first?
|
||
2. Is every recursive branch thunked with lazy eliminators?
|
||
3. Is `y` applied inside the public wrapper, not exposed as a top-level worker value?
|
||
4. Are recursive self-calls visibly progressing on consumed data?
|
||
5. Are recursive state updates constant-time?
|
||
6. Are `take`, `drop`, `nth`, `length`, `startsWith?`, or byte slicing used on abstract data?
|
||
7. Could a known counter, index, prefix, or length drive recursion?
|
||
8. Are fixed-format fields parsed with finite destructuring helpers?
|
||
9. Does any branch construct dynamic paths/responses from abstract data using recursive list helpers?
|
||
10. Can the definition normalize with all runtime arguments still unknown?
|
||
```
|