iter loop-recur.1: additive Term::Loop / Term::Recur / LoopBinder foundation
First of three iterations of the standalone loop/recur milestone
(spec 97c1ed1). loop/recur become real parseable / printable /
round-trippable / hash-stable strictly-additive AST nodes across
every no-wildcard exhaustive Term match in all six crates, plus
parse/print, prose arms, DESIGN.md + form_a.md schema blocks,
drift/coverage/hash anchors, and the spec's worked sum_to program
as a green round-trip fixture. NO typecheck semantics and NO real
codegen this iter by design: synth and lower_term are stubbed
CheckError::Internal / CodegenError::Internal exactly as mut.1
(real typecheck = iter 2, real loop-header/phi/back-edge = iter 3).
Two binding Boss design calls recorded in the plan header and the
journal: (1) verify_tail_positions "byte-unchanged" = its tail-app
verification role is unchanged, not its source — it is an
exhaustive no-wildcard match so two descent-only arms are
mandatory; acceptance evidence is the tail-app non-regression test.
(2) codegen is in iter-1 scope as stubs/pass-throughs because the
workspace must compile for cargo test --workspace.
Three plan-pseudo-vs-reality substitutions (serde Type::Con
literal, bare Int -> (con Int), unqualified LoopBinder) and one
recon-undercount integration-test site, all intent-preserving and
journalled. Boss forward-fix folded in: the committed specs
themselves wrote bare Int in the loop-binder snippets (parses as
Type::Var) — corrected the three headline snippets to (con Int) for
doc-honesty (no design/schema change).
cargo test --workspace 600 -> 608 / 0 red (Boss-reran
independently); iter13a + new loop_recur hash pins green.
This commit is contained in:
@@ -2420,6 +2420,25 @@ are real surface forms.
|
||||
{ "t": "assign",
|
||||
"name": "<id>",
|
||||
"value": Term }
|
||||
|
||||
// loop-recur iter 1: strict iteration block. `binders` declares
|
||||
// one or more loop parameters (name, type, init), evaluated in
|
||||
// order on loop entry; `body` is in scope of all binders. The
|
||||
// loop's value is `body`'s value on the iteration that exits via a
|
||||
// non-`recur` branch. Strictly additive (no `skip_serializing_if`;
|
||||
// pre-existing fixtures hash bit-identically — none carry the tag).
|
||||
// No totality claim — an infinite loop is legal. See
|
||||
// `docs/specs/2026-05-17-loop-recur.md`.
|
||||
{ "t": "loop",
|
||||
"binders": [ { "name": "<id>", "type": Type, "init": Term }, ... ],
|
||||
"body": Term }
|
||||
|
||||
// loop-recur iter 1: re-enter the lexically innermost enclosing
|
||||
// `loop`, rebinding its binders positionally to `args`. Transfers
|
||||
// control (no fall-through); valid only in tail position of its
|
||||
// enclosing loop (enforced at typecheck, `recur-not-in-tail-position`).
|
||||
{ "t": "recur",
|
||||
"args": [ Term, ... ] }
|
||||
```
|
||||
|
||||
In the MVP, `do` is only a direct call to a built-in effect op (no
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# iter loop-recur.1 — Additive AST-Node Foundation (Term::Loop / Term::Recur / LoopBinder)
|
||||
|
||||
**Date:** 2026-05-17
|
||||
**Started from:** a5eebcd5fd9d703cc4f8a6374f95e00061d86c55
|
||||
**Status:** DONE
|
||||
**Tasks completed:** 7 of 7
|
||||
|
||||
## Summary
|
||||
|
||||
`loop` / `recur` become real, parseable, printable, round-trippable,
|
||||
hash-stable, strictly-additive AST nodes across all six crates plus
|
||||
the drift tests — with NO typecheck semantics and NO real codegen
|
||||
lowering (those are loop-recur iters 2 and 3). Added `Term::Loop {
|
||||
binders, body }`, `Term::Recur { args }`, and `struct LoopBinder`
|
||||
(mirroring `MutVar` exactly); Form-A `(loop (NAME TYPE INIT)* BODY+)`
|
||||
/ `(recur ARG*)` parse + print + grammar/notes; prose render /
|
||||
count_free_var / subst_var arms; ~30 structural pass-through walker
|
||||
arms; the two semantic dispatch points stubbed with
|
||||
`CheckError::Internal` (`synth`) / `CodegenError::Internal`
|
||||
(`lower_term`) exactly as mut.1 did; DESIGN.md + form_a.md schema
|
||||
blocks; drift/coverage/hash anchors; and the spec's worked `sum_to`
|
||||
program as a green `examples/loop_sum_to.ail` round-trip fixture.
|
||||
`cargo test --workspace` 608 green / 0 red; the iter13a + new
|
||||
loop-recur hash pins both green (additivity proven — pre-existing
|
||||
canonical-JSON hashes byte-stable); all `tail`-filtered tests green
|
||||
(Boss-call-1 operational evidence).
|
||||
|
||||
## Per-task notes
|
||||
|
||||
- iter loop-recur.1.1: Core AST — `Term::Loop`/`Term::Recur` variants
|
||||
+ `struct LoopBinder` + 2 serde round-trip unit tests. RED-first
|
||||
(`error[E0599]`); GREEN after Task 4 (desugar/workspace are *in*
|
||||
ailang-core, so the lib's own exhaustive matches gate it — see
|
||||
Concerns). Both serde tests green.
|
||||
- iter loop-recur.1.2: Surface parser (`parse_loop`/`parse_recur` +
|
||||
head dispatch + unknown-head message), printer arms, form_a.md
|
||||
grammar+notes. Binder/body boundary disambiguated via an
|
||||
`is_term_head_kw` discriminator (the plan's flagged "one genuine
|
||||
parser judgement"). `parse_loop_and_recur_round_trip_via_term`
|
||||
green (exactly 2 binders + `if` body + 2-arg recur).
|
||||
- iter loop-recur.1.3: Prose projection — `write_term_prec`,
|
||||
`count_free_var`, `subst_var_with_term` Loop/Recur arms (verbatim
|
||||
plan code, lexical-shadow-aware mirroring the Mut arms).
|
||||
- iter loop-recur.1.4: ailang-core walkers — 9 desugar.rs sites + 2
|
||||
workspace.rs sites. `cargo build -p ailang-core` GREEN.
|
||||
- iter loop-recur.1.5: ailang-check — 14 steps across 7 files
|
||||
including the Boss-call-1 `verify_tail_positions` two mandatory
|
||||
additive arms and the Boss-call-2 `synth` `CheckError::Internal`
|
||||
stub. `cargo build -p ailang-check` GREEN.
|
||||
- iter loop-recur.1.6: ailang-codegen + ail/main.rs — `lower_term`
|
||||
`CodegenError::Internal` stub + real structural pass-through at
|
||||
the pure analysis walkers (lambda/escape ×3) + `synth_with_extras`
|
||||
pass-through + main.rs walk_term/rewrite_term. `cargo build
|
||||
--workspace` GREEN.
|
||||
- iter loop-recur.1.7: DESIGN.md + form_a.md schema blocks;
|
||||
design_schema_drift + spec_drift + schema_coverage anchors; new
|
||||
loop_recur hash pin; `examples/loop_sum_to.ail` positive fixture.
|
||||
Full suite 608/0; tail-app non-regression green.
|
||||
|
||||
## Boss design calls (mirrored from the plan header at iter close)
|
||||
|
||||
1. **`verify_tail_positions` "byte-unchanged" = tail-app role
|
||||
unchanged, NOT source frozen.** `verify_tail_positions`
|
||||
(`crates/ailang-check/src/lib.rs`) is an exhaustive no-`_`-wildcard
|
||||
`match`-on-`Term`; the two additive variants *require* two new
|
||||
arms there or `ailang-check` does not compile. The added arms
|
||||
only define how the tail-app walker descends through two
|
||||
brand-new node kinds that no pre-existing fixture contains —
|
||||
zero behaviour change for any pre-existing construct. The
|
||||
acceptance evidence is the tail-app non-regression test (all
|
||||
`tail`-filtered tests green), NOT a frozen-source diff. mut.1 set
|
||||
the exact precedent (it added `Term::Mut`/`Term::Assign` arms
|
||||
inside the same function). Settled; not reopened — the
|
||||
spec-compliance phase did NOT emit `unclear`/BLOCKED on the
|
||||
anticipated "spec says byte-unchanged but the function changed"
|
||||
tension, per the plan header's authority.
|
||||
2. **Codegen IS in iter-1 scope as stubs/pass-throughs (mirrors
|
||||
mut.1 site-for-site).** "No codegen in iter 1" means no real
|
||||
loop-header/phi/back-edge lowering (that is iter 3). But
|
||||
`ailang-codegen` carries no-wildcard exhaustive `Term` matches;
|
||||
the workspace would not compile without arms. Resolved exactly
|
||||
as mut.1: `CodegenError::Internal` stub at the `lower_term`
|
||||
dispatch + real structural pass-through at the pure analysis
|
||||
walkers (lambda.rs collect_captures, escape.rs walk/escapes/
|
||||
collect_free_vars) and `synth_with_extras`. No real loop lowering
|
||||
written this iter.
|
||||
|
||||
## Concerns
|
||||
|
||||
- DONE_WITH_CONCERNS (iter loop-recur.1.1/1.2/1.4): three
|
||||
plan-pseudo-vs-reality substitutions, all of the recurring
|
||||
"specs need concrete code / plan pseudo vs reality" class
|
||||
(`feedback_specs_need_concrete_code`, `feedback_plan_pseudo_vs_reality`),
|
||||
each resolved by preserving the arm/test's evident intent and
|
||||
fixing to the real codebase:
|
||||
1. **Task-1 serde expected-bytes literal.** The plan asserted
|
||||
`"type":{"t":"con","name":"Int","args":[]}`; the real canonical
|
||||
`Type::Con` form is `{"k":"con","name":"Int"}` (discriminator
|
||||
`"k"`, empty `args` omitted — established since mut.2). The
|
||||
test's stated intent (pin one-binder Loop canonical bytes;
|
||||
prove `binders` has no `skip_serializing_if`) is preserved;
|
||||
only the mis-transcribed expected string was corrected. NOT a
|
||||
bug-dodge — `{"k":"con",…}` is the canonical form every Type
|
||||
fixture uses; the plan author mis-recalled the Type JSON.
|
||||
2. **Bare `Int` vs `(con Int)` in binder triples.** The spec's
|
||||
worked example and the plan's Step-1 test source / Step-6
|
||||
fixture write `(loop (acc Int 0) …)` with bare `Int`. The real
|
||||
`parse_type` parses a bare `Int` ident as `Type::Var{"Int"}`
|
||||
(a type *variable*), not the Int constructor — semantically
|
||||
wrong and inconsistent with every `mut` fixture
|
||||
(`(var x (con Int) 0)`), the spec schema, and the Task-1 serde
|
||||
pin. Corrected to `(con Int)` in the parser test source and the
|
||||
`examples/loop_sum_to.ail` fixture. This is the LLM-natural AND
|
||||
correct Form-A type (not a fixture-adapts-to-bug case — every
|
||||
sibling fixture uses `(con Int)`).
|
||||
3. **`ailang_core::ast::LoopBinder` path inside ailang-core.** The
|
||||
plan's literal desugar.rs/workspace.rs arms used the
|
||||
downstream-crate path `ailang_core::ast::LoopBinder`; inside
|
||||
ailang-core itself (`use crate::ast::*;`) the correct path is
|
||||
unqualified `LoopBinder` (consistent with the adjacent bare
|
||||
`MutVar` usage). `subst_call_with_extras`'s plan arm also used
|
||||
`(target, extras)`; the real signature is `(name, lifted,
|
||||
extras)` — mirrored from the local Mut arm per the plan's own
|
||||
instruction. ailang-check (downstream) correctly keeps the
|
||||
`ailang_core::ast::LoopBinder` path.
|
||||
|
||||
## Boss addendum (post-orchestrator, at commit)
|
||||
|
||||
Concern 2 surfaced that the bare-`Int`-in-binder imprecision is not
|
||||
just in the plan/test but in the **committed specs themselves**:
|
||||
`docs/specs/2026-05-17-loop-recur.md`'s headline clause-1 worked
|
||||
example + the must-fail `bad_recur` fixture, and
|
||||
`docs/specs/2026-05-17-llm-surface-discipline.md` §5's illustrative
|
||||
anchor, all wrote `(loop (acc Int 0) (i Int 1) …)`. Bare `Int`
|
||||
parses as `Type::Var{"Int"}`, so the spec's *headline evidence* —
|
||||
the program an LLM author would copy verbatim — does not parse to
|
||||
the typed loop it claims. Same doc-honesty class as the mono.rs
|
||||
header / effect-doc-honesty: a spec must show code that actually
|
||||
parses to what it asserts. Boss forward-fixed all three snippets to
|
||||
`(con Int)` (the established convention every `mut` fixture + the
|
||||
spec's own schema use); folded into this iter's commit. No design,
|
||||
semantics, schema, or load-bearing assumption changed — a
|
||||
surface-syntax precision fix, so no re-brainstorm / re-grounding.
|
||||
|
||||
## Known debt
|
||||
|
||||
- No additional E2E fixture written (Phase 3). iter-1 ships NO
|
||||
typecheck and NO codegen by design; an `ail run` e2e would only
|
||||
exercise the deliberately-temporary `synth`/`lower_term`
|
||||
`Internal` stubs (a negative-value test asserting a transient
|
||||
error). The milestone invariant worth protecting at iter-1 —
|
||||
additivity (hash pins), round-trip (the new fixture), drift trio,
|
||||
tail-app non-regression — is fully covered by tests added in
|
||||
Tasks 1/2/7. The spec's positive sum_to-runs-to-a-value E2E is
|
||||
explicitly iter-3 scope.
|
||||
|
||||
## Files touched
|
||||
|
||||
- AST + core walkers: `crates/ailang-core/src/ast.rs`,
|
||||
`desugar.rs`, `workspace.rs`
|
||||
- Surface: `crates/ailang-surface/src/parse.rs`, `print.rs`;
|
||||
`crates/ailang-core/specs/form_a.md`
|
||||
- Prose: `crates/ailang-prose/src/lib.rs`
|
||||
- Check: `crates/ailang-check/src/{lib,lift,mono,linearity,
|
||||
uniqueness,reuse_shape,pre_desugar_validation}.rs`
|
||||
- Codegen + CLI: `crates/ailang-codegen/src/{lambda,escape,lib}.rs`,
|
||||
`crates/ail/src/main.rs`
|
||||
- Docs/drift/coverage/hash: `docs/DESIGN.md`,
|
||||
`crates/ailang-core/tests/{design_schema_drift,spec_drift,
|
||||
schema_coverage,hash_pin}.rs`,
|
||||
`crates/ail/tests/codegen_import_map_fallback_pin.rs`
|
||||
(recon-undercount: an integration-test exhaustive `Term` match
|
||||
not in the plan's site inventory; same additive-arm class,
|
||||
mirrors the Mut defensive-recursion shape — Boss-call-2-class
|
||||
"recon under-scoped it")
|
||||
- New fixture: `examples/loop_sum_to.ail`
|
||||
|
||||
## Stats
|
||||
|
||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.1.json
|
||||
@@ -81,3 +81,4 @@
|
||||
- 2026-05-16 — iter revert: Iteration-discipline milestone (it.1 `96db54d` + it.2 `a4be1e5`) fully backed out by one **forward** iteration (`main` sacrosanct — never rewound; `1ff7e81`, the pre-`9973546` commit, is the per-region byte oracle). Root cause: the milestone was an over-escalation of fieldtest finding F1 (a `[friction]` item whose own minimal recommendation was a DESIGN.md note); its totality dichotomy made the maximally-LLM-natural `build(d:Int)=Node(1,build(d-1),build(d-1))` inexpressible (it.3 BLOCKED), and the only in-thesis escape (A1/it.2b) conceded the language's first documented-unenforced totality precondition — a purity-pillar dilution the user rejected. `Term::Loop`/`Term::Recur`/`LoopBinder`, the `verify_structural_recursion` guardedness pass + `term_contains_loop` + `Diverge`-injection + the transitively-it.2 `module_fns` plumbing, the five `Recur*`/`NonStructuralRecursion` `CheckError` variants (+ `code()` + 3 dedicated `ctx()` arms), the it.1 codegen loop-header/phi/back-edge + parallel `block_terminated` setter, and all walker arms are removed — `crates/ailang-check/src/lib.rs` and every reverted source/test file byte-identical to `1ff7e81`. Surgical keeps: feature-acceptance **clause 3** (DESIGN.md + brainstorm SKILL.md, worked example de-claimed "shipped"→hypothetical) and the F3 P2 todo. Sole net addition: an honest F1/F4 documented-idiom note (tail-recursive accumulator fallback; `examples/mut_counter.ail`) guarded by a doc-presence test. 16 it.1/it.2 fixtures + 2 pin files + `bench/it3-oracle/` deleted; 2 RC fixtures restored to `1ff7e81`; `bench/orchestrator-stats/2026-05-15-iter-it.{1,2,3}.json` kept (historical record). Roadmap: Iteration-discipline block + blocking-fork section removed; the genuine total-Int-recursion ambition preserved as a deferred P2 milestone sequenced behind a future `Nat`/refinement-types milestone (not abandoned — correctly sequenced). Correctness gate PRISTINE: 164 surviving `1ff7e81`-era fixtures `ail check`/`ail run` byte-identical to pre-milestone behaviour (1ff7e81 worktree reference compiler, zero drift); `cargo test --workspace` 600/0; zero residual it.1/it.2 production surface. The old it.* journals/plans + the superseded-headered `2026-05-15-iteration-discipline.md` stay as historical record → 2026-05-16-iter-revert.md
|
||||
- 2026-05-16 — audit iteration-discipline-revert (milestone close, clean): architect confirmed the revert byte-pristine (18 src + 5 test files byte-identical to `1ff7e81`, zero residual it.1/it.2 production surface, DESIGN.md coherent, roadmap/spec hygiene correct); one `[medium]` drift — a dangling `loop`/`recur` cross-reference into the reverted milestone inside the *live* Stateful-islands roadmap scope bullet — fixed inline (Boss doc-hygiene; reworded to keep the real "no `while`; repetition stays recursion" scope guard, drop the reverted-milestone reference). Bench: `compile_check.py`/`cross_lang.py` exit 0; `check.py` exit 1 on the single metric `bench_list_sum.bump_s` +12.71%. Bencher localisation: HEAD and `1ff7e81` bump binaries `cmp`-identical, interleaved 3×60-run measurement shows head−oracle delta ~0 (±1.5% stdev) with *both* ~+11% over the 2026-05-09 baseline → environmental/hardware drift, NOT a revert regression (third independent proof codegen == `1ff7e81`). Resolution: carry-on, baseline NOT ratified (Iron Law — nothing intentionally moved the metric; byte-identical codegen must not move the baseline; the spec's PRISTINE mandate is satisfied because pristine = "no regression introduced", proven). Pre-existing `*.bump_s` baseline-vs-hardware staleness (the `1ff7e81` oracle trips the same +11%) filed forward as a separate P2 `[todo]` (bench-harness recalibration, no language change), not mis-attributed to the revert. Fieldtest skipped (revert restores the already-field-tested pre-`9973546` surface; Task-11 164-fixture byte-equivalence is the stronger proof). Milestone closed clean → 2026-05-16-audit-iteration-discipline-revert.md
|
||||
- 2026-05-16 — iter effect-doc-honesty: standalone documentation-honesty tidy (split out from the retired effect-op-arg-modes bundle — user rejected bundling a real DESIGN.md fix with speculative build-ahead infra). Corrected three false effect-system claims the recon surfaced: DESIGN.md Decision 3 "row-polymorphic (`![IO | r]`)" (no `EffectRow`/row var exists — effect sets are flat closed sets unified by set-equality) + "`IO` and `Diverge` … are wired up" (`Diverge` is 100% vapour: zero code in any crate) → reconciled to IO-only/Diverge-reserved-unimplemented (modelled on Decision 4's reserved-refinements precedent); DESIGN.md §"What is not supported" "IO and Diverge ops" bullet; `ast.rs` `Term::Do` doc-comment "resolved against the effect-handler table at link time" → the real mechanism (typecheck `Env::effect_ops` lookup + `lower_effect_op` literal codegen match, no handler table). Satellite lockstep: `form_a.md:226` + rule-3, `main.rs:289` merge-prose CONTRACT example. Guarded by a new 4-test doc-presence pin `crates/ailang-core/tests/effect_doc_honesty_pin.rs` (fiction-absent + corrected-anchor-present, single-line substrings — wrap-robust). No language/checker/codegen change; `ail check`/`run` byte-unchanged by construction. `cargo test --workspace` 600 → 604; drift/coverage trio (`design_schema_drift`/`spec_drift`/`schema_coverage`) green, empirically confirming none scans the effect-prose region. One concern: the plan's Task-2 replacement body soft-wrapped a phrase the single-line pin asserts; orchestrator resolved correctly (exact wording + exact pin preserved, wrap column moved) — recurring-class planner gap, fixed forward by a Step-5 self-review tightening → 2026-05-16-iter-effect-doc-honesty.md
|
||||
- 2026-05-17 — iter loop-recur.1: standalone-`loop`/`recur` milestone (1 of 3) — the strictly-additive AST-node foundation. `Term::Loop { binders: Vec<LoopBinder>, body }` / `Term::Recur { args }` / `struct LoopBinder` (mirrors `MutVar`'s `(name,type,init)` triple; `binders` no `skip_serializing_if`) added end-to-end across all six crates' no-`_`-wildcard exhaustive `Term` matches (~30 sites incl. one recon-undercount integration-test pin): Form-A `parse_loop`/`parse_recur` (binder/body boundary disambiguated by an `is_term_head_kw` guard — the plan's flagged sole parser judgement) + print + `form_a.md` grammar/notes + prose render/free-var/subst lockstep + canonical-JSON serde/round-trip + DESIGN.md §Data-model `"t":"loop"`/`"t":"recur"` blocks + design_schema_drift/spec_drift/schema_coverage anchors + a new `loop_recur` hash pin (additivity proven: iter13a + new pin both green, pre-existing canonical-JSON bytes byte-stable) + `examples/loop_sum_to.ail` round-trip fixture. NO typecheck semantics + NO real codegen by design: the two semantic dispatch points stubbed `synth`→`CheckError::Internal` / `lower_term`→`CodegenError::Internal` exactly as mut.1; pure analysis walkers (escape/lambda/`synth_with_extras`) get real structural pass-through. Two binding Boss design calls (mirrored into the journal): (1) `verify_tail_positions` "byte-unchanged" = tail-app *role* unchanged not source-frozen — it is an exhaustive no-wildcard match so two additive descent-only arms are mandatory; acceptance evidence is the tail-app non-regression test (all `tail`-filtered tests byte-identical), mut.1 set the precedent; (2) codegen IS iter-1 scope as stubs/pass-throughs (no real loop-header/phi/back-edge — that is iter 3). Three plan-pseudo-vs-reality substitutions, all intent-preserving, recurring `feedback_specs_need_concrete_code`/`feedback_plan_pseudo_vs_reality` class: serde `Type::Con` literal `{"t":"con",…,"args":[]}`→real `{"k":"con","name":"Int"}`; bare `Int`→`(con Int)` in binder triples (bare `Int` parses as `Type::Var`); `ailang_core::ast::LoopBinder`→unqualified inside ailang-core. Boss forward-fix folded in: Concern 2 surfaced that the committed specs themselves (`2026-05-17-loop-recur.md` headline + `bad_recur`, `2026-05-17-llm-surface-discipline.md` §5) wrote bare `Int` — corrected all three snippets to `(con Int)` (doc-honesty, same class as the mono.rs header; no design/schema/assumption change, no re-brainstorm). `cargo test --workspace` 600→608 / 0 red (Boss-reran independently). Component 4 (typecheck) = iter 2; Component 5 (codegen) + positive/deep-`n` E2E = iter 3 → 2026-05-17-iter-loop-recur.1.md
|
||||
|
||||
@@ -138,7 +138,7 @@ sugar; loop state is rebound parameters, not mutation):
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (acc Int 0) (i Int 1)
|
||||
(loop (acc (con Int) 0) (i (con Int) 1)
|
||||
(if (app > i n)
|
||||
acc
|
||||
(recur (app + acc i) (app + i 1))))))
|
||||
|
||||
@@ -106,7 +106,7 @@ What an LLM author writes for "sum 1..n" **with** `loop`/`recur`:
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (acc Int 0) (i Int 1)
|
||||
(loop (acc (con Int) 0) (i (con Int) 1)
|
||||
(if (app > i n)
|
||||
acc
|
||||
(recur (app + acc i) (app + i 1))))))
|
||||
@@ -143,7 +143,7 @@ is rejection)
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (i Int 0)
|
||||
(loop (i (con Int) 0)
|
||||
(app + 1 (recur (app + i 1)))))) ; recur is an argument to +,
|
||||
; NOT tail → RecurNotInTailPosition
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user