Skip to content

Evaluate dropping subroutine hopper — call('foo') could wrap foo::1 directly #85

@mellonis

Description

@mellonis

Background

PostMachine constructs a separate hopper State for each subroutine, named after the subroutine itself (e.g., rightToBlank). The hopper's only transition forwards to the first instruction ([ifOtherSymbol]: { nextState: rightToBlank::1 }). call('foo') wraps the hopper:

// commands.ts ~107
const state = subroutineInitialStates[subroutineName]  // ← the hopper
  .withOverriddenHaltState(new State({
    [ifOtherSymbol]: { nextState: nextInstruction },
  }, continuationName));

This dates from v6.1.0's instruction-derived naming work (per packages/machine/CLAUDE.md).

The asymmetry

Level Entry-point structure
Top-level program idle -. enter .-> 1 — idle sentinel points directly at the first numbered instruction. No hopper.
Subroutine foo caller → wrapper → call ==> foo (hopper) → "[*]" → foo::1 — extra hopper hop before the first instruction.

Surfaced while writing the callable-subtree visualization spec for turing#174 — the new diagram model makes the asymmetry visually obvious (the hopper appears as a bare inside the subtree, adding an extra node between the wrapper's call arrow and the first body instruction).

Proposed change

Drop the hopper. call('foo') directly wraps foo::1:

const state = subroutineFirstInstruction[subroutineName]  // foo::1
  .withOverriddenHaltState(continuation);

subroutineInitialStates[name] either renamed (to subroutineFirstInstruction) or eliminated as redundant with the existing instruction-state cache.

What changes

Composite wrapper name: foo(continuation)foo::1(continuation). Slightly less readable but accurately reflects the bare's identity.

Per-subroutine state count: -1 State per subroutine (the hopper). Small memory + GraphNode count win, especially for libraries with many subroutines.

Symmetry: top-level and subroutine both have a "first numbered instruction is the entry" model. Reads consistently across nesting depths.

Engine emit (under turing#174's callable-subtree model): subtree_foo::1 contains foo::1, foo::2, halt marker. Wrapper [[foo::1(1~2)]] calls into the subtree's foo::1 bare directly. One less node per subroutine in the diagram.

Trade-offs / concerns

  1. Composite-name readability. foo(continuation) is more "subroutine-call-like" than foo::1(continuation). The latter accurately names the bare but obscures the subroutine name. Mitigation: PostMachine could still expose subroutineName as a separate field on the wrapper for display purposes (e.g., in summarizePostMachine).

  2. Breakpoint registry / instruction-derived paths. PostMachine's debug lockdown registers each per-instruction State separately. The hopper had its own registry entry under the subroutine's name. Dropping it removes that registry entry — paths that referenced the hopper (pm.setBreakpoint("foo"), etc.) would need to target "foo::1" instead. Documented migration needed.

  3. Backward compat. Diagram emit shape changes (one less node per subroutine, different composite-name). Tests that pin state names would need updates. summarize* output may differ. The states.md artifacts for library-binary-numbers would regenerate.

  4. stateAt(path) lookup. Currently pm.stateAt('foo') returns the hopper. Post-change, what does it return? The first instruction (foo::1)? Or throw? Spec a clear migration.

Alternative (briefly considered)

Render the hopper as a visualization-only sentinel (stadium shape s([…]) like idle) without dropping it. Preserves the runtime State but makes the asymmetry meaningful (different shape = different role). Rejected as a separate engine-spec change; the symmetry argument here is structural, not just visual.

Scope

  • Single-package change in @post-machine-js/machine.
  • v6.x major or v7.x — likely needs a major version bump (breaking change to wrapper composite names + breakpoint paths + diagram shape).
  • Coordinate with turing#174 — once both ship, the PostMachine subroutine diagram becomes clean and symmetric with top-level.

Related

  • turing-machine-js#174 — callable-subtree visualization (where this asymmetry first surfaced visibly).
  • post-machine-js v6.1.0 work that introduced instruction-derived names.

Metadata

Metadata

Assignees

No one assigned

    Labels

    v7Targets v7 release (composition-representation overhaul)

    Projects

    Status
    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions