Files
tricu/notes/tricu-normalization-rules.md

249 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 workers 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?
```