Skip to content

types/patricia_trie: fold the stack instead of indexing it - #1582

Merged
sergey-shandar merged 3 commits into
mainfrom
claude/todo-implementation-rifq4g
Aug 15, 2026
Merged

types/patricia_trie: fold the stack instead of indexing it#1582
sergey-shandar merged 3 commits into
mainfrom
claude/todo-implementation-rifq4g

Conversation

@sergey-shandar

Copy link
Copy Markdown
Contributor

Implements fjs/types/patricia_trie/todo/declarative-stack-fold.md (deleted here).

end

It was a right fold written as a descending index loop with a let and a mutating destructuring assignment:

end: ([storage, stack]) => {
    if (stack.length === 0) { return [undefined, storage] }
    let h = stack[stack.length - 1][1]
    for (let i = stack.length - 2; i >= 0; i--) {
        const lHash = stack[i][1];
        [h, storage] = create(lHash, h, storage)
    }
    return [h, storage]
}

splitLast supplies the seed and the rest together, so the empty stack becomes the null branch and the separate length guard disappears:

end: ([storage, stack]) => {
    const split = splitLast(stack)
    if (split === null) { return [undefined, storage] }
    const [rest, [, lastHash]] = split
    return rest.reduceRight(([h, s], [, lHash]) => create(lHash, h, s), [lastHash, storage])
}

No let, no index arithmetic, no mutation.

push

The TODO's call — that push's loop is genuine carry propagation and can stay imperative, but should split rather than index — is right: each merge decides whether the next one happens, so it is sequential in a way end is not. What it does not need is stack[stack.length - 1], stack[stack.length - 2] and stack.slice(0, -2), which are now two splitLast calls. Either split answering null is exactly where the old stack.length >= 2 guard stopped, so the guard is not lost, just expressed by the same operation that reads the elements.

Verification

This builds content-addressed trees, so identical structure matters more than a green suite:

  • npx tsc clean.
  • fjs test: 2797 pass, 0 fail.
  • npm run cov: fjs/types/patricia_trie/module.f.mjs at 100% lines/branches/functions.
  • Differential check against main. 246 sorted leaf sequences — every length 0 through 40, six deterministic pseudo-random 64-bit sets each — pushed one at a time and drained with end. Compared the root identity, the node count, and every (left, right, hash) triple in storage: 406 KB of output, byte-identical. That covers the empty stack, the single-leaf stack (where end returns the seed without folding), and deep carry chains.

One note on the splitLast dependency: its elements here are Candidate<T> tuples, never null, so the nullish-element defect filed as fjs/types/array/todo/split-accessors-nullish-element.md cannot bite this caller.

Changelog:

  • types/patricia_trie: end is a right fold over the candidate stack and push splits instead of indexing; identical output

🤖 Generated with Claude Code

https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs


Generated by Claude Code

Working notes; the PR title and description are the commit message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs
@sergey-shandar sergey-shandar changed the title types/patricia_trie: fold the stack instead of walking it by index types/patricia_trie: fold the stack instead of indexing it Aug 15, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript 899ccd1 Commit Preview URL

Branch Preview URL
Aug 15 2026, 07:41 AM

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the fold is behavior-preserving, not just shape-preserving.

  • end: splitLast gives rest = stack[0..len-2] plus lastHash, and reduceRight visits len-2 … 0 — the same order as the old descending loop — threading [h, storage] through create in the same sequence, so storage accumulates identically. Empty maps to [undefined, storage], single-element to the untouched seed.
  • push: the double splitLast reproduces the old [len-1]/[len-2]/slice(0,-2), and the two null breaks fire on exactly lengths 0 and 1 that the old length >= 2 guard rejected. splitLast's last(a) === null ambiguity is moot here — stack elements are Candidate tuples, never null.

npm test green (2797 pass / 0 fail). Also ran a 20k-case differential fuzz (0–8 leaves) of old vs new with an order-sensitive create keyed on storage.length: identical root hash and identical storage node sequence in every case.

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 15, 2026
Merged via the queue into main with commit 4f4cf1d Aug 15, 2026
19 checks passed
@sergey-shandar
sergey-shandar deleted the claude/todo-implementation-rifq4g branch August 15, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants