Fully normalized top-level definitions
This commit is contained in:
@@ -1,81 +0,0 @@
|
||||
# Recursive Consumer Argument Order
|
||||
|
||||
## Rule
|
||||
|
||||
Put consumed data first in recursive workers in `tricu` code.
|
||||
|
||||
*AVOID* this shape:
|
||||
|
||||
```text
|
||||
worker control state input
|
||||
```
|
||||
|
||||
*USE* this shape:
|
||||
|
||||
```text
|
||||
worker input control state
|
||||
```
|
||||
|
||||
The consumed structure should block recursion when it is unknown. Counters, indexes, lengths, and accumulator state should not be able to drive recursion over abstract input.
|
||||
|
||||
## Bad shape
|
||||
|
||||
The original `readBytes_` worker put loop-control arguments before the byte stream:
|
||||
|
||||
```tricu
|
||||
readBytes_ = y (self n i bs original acc :
|
||||
matchBool
|
||||
(ok (reverse acc) bs)
|
||||
(matchResult
|
||||
(code rest : err code original)
|
||||
(actual rest :
|
||||
self n (succ i) rest original (pair actual acc))
|
||||
(readU8 bs))
|
||||
(equal? i n))
|
||||
|
||||
readBytes = (n bs : readBytes_ n 0 bs bs t)
|
||||
```
|
||||
|
||||
With a partial application like:
|
||||
|
||||
```tricu
|
||||
readBytes 2
|
||||
```
|
||||
|
||||
the evaluator knows `n = 2` and `i = 0`, but `bs` is still abstract. That lets the counter check drive recursive specialization before the byte stream is available, which can build a huge symbolic residual tree. This has been proven; do not reason about it further.
|
||||
|
||||
## Good shape
|
||||
|
||||
The corrected worker takes the byte stream first and immediately case-analyzes it:
|
||||
|
||||
```tricu
|
||||
readBytes_ = y (self bs n i original acc :
|
||||
matchList
|
||||
(matchBool
|
||||
(ok (reverse acc) bs)
|
||||
(err errUnexpectedEof original)
|
||||
(equal? i n))
|
||||
(h r :
|
||||
matchBool
|
||||
(ok (reverse acc) bs)
|
||||
(self r n (succ i) original (pair h acc))
|
||||
(equal? i n))
|
||||
bs)
|
||||
|
||||
readBytes = (n bs : readBytes_ bs n 0 bs t)
|
||||
```
|
||||
|
||||
Now:
|
||||
|
||||
```tricu
|
||||
readBytes 2
|
||||
```
|
||||
|
||||
becomes a function waiting on `bs`. Since the worker immediately performs `matchList ... bs`, evaluation blocks on the missing input instead of unrolling the counter loop.
|
||||
|
||||
## Takeaway
|
||||
|
||||
```text
|
||||
Let consumed data drive recursion.
|
||||
Do not let counters unroll over abstract input.
|
||||
```
|
||||
248
notes/tricu-normalization-rules.md
Normal file
248
notes/tricu-normalization-rules.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# 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?
|
||||
```
|
||||
Reference in New Issue
Block a user