Files
tricu/notes/recursive-consumers.md

1.9 KiB

Recursive Consumer Argument Order

Rule

Put consumed data first in recursive workers in tricu code.

AVOID this shape:

worker control state input

USE this shape:

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:

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:

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:

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:

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

Let consumed data drive recursion.
Do not let counters unroll over abstract input.