Unit 6d: let, list*, the list library, and the shared puzzle harness - #65
Merged
Conversation
Unit 6d in docs/execution-plan.md, on the evidence of authoring observations 1 and 4 in docs/puzzles/singleton.md. let is parallel, matching the modern Chialisp dialect, and compiles as the naming helpers it replaces are written by hand: the bound names become parameters and the body is applied once, in place, with the bound values consed in front of the arguments so the function tree stays at path 2. list* conses its items onto its last argument, the tail, a purely syntactic expansion recorded as a divergence because no Chialisp dialect has the form. Reserved-word growth is a deliberate compatibility break for source that used either spelling as a name, pinned by the symbol-loader test when the implementation lands.
Unit 6d in docs/execution-plan.md, authoring observation 4 in docs/puzzles/singleton.md: both landed puzzles build condition lists as chained c calls because list cannot end in a tail. The form is the list fold seeded with the compiled last argument instead of nil, purely syntactic, and a lone tail compiles bare on the single-operand assert precedent. (list*) is a compile error. Chialisp has no such form, recorded as a divergence in the language reference. The reserved-word growth is a deliberate compatibility break, pinned by the dispatch test and the symbol-loader test.
Unit 6d in docs/execution-plan.md, authoring observation 1 in docs/puzzles/singleton.md: naming a value once is the dominant authoring cost, and both landed puzzles hand-wrote helpers whose only purpose is a binding. The bound names become parameters of the body, applied once in place: the environment is rebuilt as the enclosing one with the bound values consed in front of the arguments, so the function tree keeps path 2, calls and recursion inside the body work unchanged, and the cost is one apply plus one cons per binding, pinned against the inline substitution it beats. Bindings are parallel, matching the modern Chialisp dialect, and a sibling reference is an unknown name, pinned. Enclosing bindings re-root under the rebuilt environment's tail: path atoms rewrite arithmetically, and a pair-valued inline substitution re-applies against the restored environment, keeping call-by-name, pinned by the used-twice and unused tests. Programs without let compile byte-identically, which the existing codegen pins and both puzzle suites' mod-hash drift guards prove.
Three cases in the vm suite: let through the function tree with an outer name read through the rebuilt environment's tail, nested let shadowing its own binding, and list* consing onto an inherited tail. Generated by bitlisp-compile and verified against the reference VM by tools/run_vectors.py.
Unit 6d in docs/execution-plan.md, authoring observation 3 in docs/puzzles/singleton.md: append, reverse with its reverse-onto accumulator, sum, and last join length and nth, each a generic first-order recursion the puzzles otherwise respell. map, filter, and member are declined for lack of production evidence: without function values there is no generic map, Chialisp's position too. Unreached declarations prune, so growth is free for existing callers, pinned by the length-only bytes recorded before the helpers joined. Each helper is tested against Python's own fold under Hypothesis, with the empty and improper-tail edges pinned.
Unit 6d in docs/execution-plan.md, authoring observation 8 in docs/puzzles/singleton.md: both puzzle test files defined the same transaction and input builders, outpoint-bound signer, curried instance derivation, run wrappers, and vector-agreement loops. puzzle_support.py now owns the puzzle-agnostic pieces, a plain module beside support.py under the same no-conftest convention, and both files keep only what the puzzle owns: sources, mod hashes, lifecycle, and case tables. Pure movement plus parameterization. fee_input takes its amount explicitly at every call site, the input builder is one taproot_input with the vault's argument order, the vault's spec-rule root reimplementation moves whole (the third puzzle wants it), and the run wrappers accept text or nodes, folding the two files' variants. Test-only: the mod-hash drift guards and the vector-agreement closures in both files pass unchanged, so no puzzle bytes and no vectors moved.
let, list*, the completed list library, and the shared puzzle harness are in, with the puzzles byte-unmoved as the unit required.
…pin, harness rename repairs Self-review of the branch. The classic unnested spelling (let (A 1) body) now errors as 'a let binding takes a name and a value' instead of complaining about the argument list, with the row pinned. The let pin gains a two-binding case showing the second name one rest step deeper and the old environment behind both consed values. The harness extraction had renamed test_unknown_path_raises through an over-broad substitution, and one vault comment still said the spec-rule reimplementation lived 'above' after it moved to puzzle_support: both repaired.
The library inverse of int_to_atom, the same reading everywhere, replacing a raw unsigned int.from_bytes that agreed with it only because path atoms are always positive.
…cons fold From the multi-agent review. A quote-headed binding evaluates the same under any environment, so it now crosses a let unwrapped instead of paying a restore-apply per reference, pinned by a disassembly test. The rewrite's argument-side invariant is enforced with a loud AssertionError instead of resting on a docstring, malformed-binding errors carry the offending entry's offset instead of the let keyword's, the two list forms share one _cons_onto fold instead of maintaining parallel copies, and the dict that becomes the body's bindings is named bound_paths for what it holds.
From the multi-agent review: the vault drift guard checked only program bytes, so a pinned solution could drift from source silently while the singleton file audited both. Every case now carries its constructed solution. The three failing trigger cases pin the captured no-revault signature reused across rewritten amounts, the replay an observer could actually attempt, which their paths reject on the amount guards before any signature runs.
From the multi-agent review. A top-level let binding only literals, in a program with no function tree, emits the curried shape, so curry.md's collision note now names both shapes. let and list* get their glossary rows beside the other authoring forms, per the same-PR rule.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Unit 6d of the execution plan, on the evidence of authoring observations 1, 3, 4, and 8 in
docs/puzzles/singleton.md: theletandlist*compiler forms, the completed list library, and a shared puzzle test harness. The two landed puzzles are not rewritten, so no puzzle bytes, mod hashes, or puzzle vectors move, and the drift guards prove it.letbinds in parallel, matching the modern Chialisp dialect, and compiles as the hand-written naming helpers do: the bound names become parameters of the body, applied once in place, the environment rebuilt as the enclosing one with the bound values consed in front of the arguments. The function tree keeps path 2, so calls and recursion inside a let body need nothing new, and the cost is one apply plus one cons per binding. Enclosing bindings re-root under the rebuilt tail: path atoms rewrite arithmetically, and pair-valued inline substitutions re-apply against the restored environment, keeping call-by-name.list*conses its items onto its last argument, the tail, a purely syntactic expansion. Chialisp has no such form, recorded in the language reference's deviations.append,reverse(withreverse-onto),sum, andlastjoinlengthandnthinpuzzles/lib/list.blib.map,filter, andmemberare declined for lack of production evidence. Pruning keeps the growth free for existing callers, pinned.python/tests/puzzle_support.pyowns the puzzle-agnostic harness both puzzle test files duplicated, roughly 190 preamble lines each: builders, the outpoint-bound signer, instance derivation, run wrappers, the spec-rule root reimplementation, and the vector-agreement audits.Authorization
Tooling, not consensus:
docs/execution-plan.mdunit 6d and the language reference commit. Nospec/change. The reserved-word growth is a deliberate compatibility break for source or symbol files namingletorlist*, pinned by the dispatch and symbol-loader tests, as units 6 and 6c recorded.Reading order
docs: let and list* join the language reference(the semantics, first)compiler: list* conses its items onto its tailcompiler: let compiles as the one-shot naming helper(the path rewrite in_letis the one subtle piece, its two invariants stated in the code)vectors: pin the unit 6d compiled representativeslib: the list library completestests: the shared puzzle harness(pure movement plus parameterization)docs: unit 6d flips to landedVerify independently
The four hashes must equal the pinned literals in the two puzzle test files, and
git diff main -- vectors/vm/vault-programs.json vectors/vm/singleton-programs.json vectors/validation/must be empty.