fieldtest: loop-recur — 10 examples, 6 findings (milestone CLOSE, clean on all axes)
Post-audit downstream-LLM-author field test of the shipped loop/recur surface (DESIGN.md + public examples only). 3 real iterative programs (Newton isqrt, Collatz, Euclidean gcd) + 5 plausible-mistake negatives + 2 no-termination probes, all run through the public ail CLI. 0 bugs. 4 working findings on the milestone's own axes: rejection diagnostics point-exact AND self-fixing; recur tail-position threads through match/let/outer-if (spec only showed if); loop composes as a value sub-expression + byte-stable round-trip; no-termination boundary exact. This empirically substantiates the "LLM author can now write iterative programs" claim. Two orthogonal non-blocking findings, neither in loop/recur scope, both routed to P2 todos (refused the scope creep into a loop/recur tidy): niladic (app f) spec_gap independently re-confirms the existing mut-local-F3 roadmap item (the design-fork decision deliberately NOT auto-ratified under /boss — parked, priority-strengthened); module-level (doc) diagnostic-hint friction (one-line tidy). Boss-verified independently (gcd->27; recur-outside-loop fires exact). The standalone loop/recur milestone is fully ratified and CLOSED: 3 iterations + tidy shipped, audit clean (drift resolved, bench pristine carry-on), fieldtest clean on every axis. Roadmap P0 marked closed.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
(module loop_recur_1_isqrt_newton
|
||||
(fn main
|
||||
(doc "Integer square root of 152399025 (= 12345^2) via Newton's method, then a sanity-difference. Expected stdout: 12345 then 0.")
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(let r (app isqrt 152399025)
|
||||
(seq (app print r)
|
||||
(app print (app - r 12345))))))
|
||||
(fn isqrt
|
||||
(doc "Newton's method for floor(sqrt(n)). Two binders: x (current guess) and prev (previous guess, to detect the fixpoint/2-cycle). The recur is buried inside a nested if, not at body toplevel.")
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(if (app < n 2)
|
||||
n
|
||||
(loop (x (con Int) n) (prev (con Int) 0)
|
||||
(if (app == x prev)
|
||||
x
|
||||
(let next (app / (app + x (app / n x)) 2)
|
||||
(if (app == next x)
|
||||
next
|
||||
(recur next x)))))))))
|
||||
@@ -0,0 +1,20 @@
|
||||
(module loop_recur_2_collatz
|
||||
(fn main
|
||||
(doc "Collatz step counts. collatz_len(27)=111, collatz_len(97)=118, collatz_len(1)=0. Expected stdout (per line): 111, 118, 0.")
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(seq (app print (app collatz_len 27))
|
||||
(seq (app print (app collatz_len 97))
|
||||
(app print (app collatz_len 1))))))
|
||||
(fn collatz_len
|
||||
(doc "Number of Collatz steps to reach 1. Two binders: n (current value) and steps (accumulator). Parity dispatch is a `match` on n%2; the recur lives in the tail of each match arm, not at body toplevel.")
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params start)
|
||||
(body
|
||||
(loop (n (con Int) start) (steps (con Int) 0)
|
||||
(if (app == n 1)
|
||||
steps
|
||||
(match (app % n 2)
|
||||
(case (pat-lit 0) (recur (app / n 2) (app + steps 1)))
|
||||
(case _ (recur (app + (app * 3 n) 1) (app + steps 1)))))))))
|
||||
@@ -0,0 +1,16 @@
|
||||
; NEGATIVE FIXTURE. The LLM-natural mistake: author writes a
|
||||
; self-recursive helper and reaches for `recur` as if it were a
|
||||
; generic self-tail-call, but there is no enclosing `loop`.
|
||||
; Expected: `ail check` exits 1 with code recur-outside-loop.
|
||||
(module loop_recur_3a_recur_outside_loop
|
||||
(fn countdown
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(if (app == n 0)
|
||||
0
|
||||
(recur (app - n 1)))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app countdown 5)))))
|
||||
@@ -0,0 +1,17 @@
|
||||
; NEGATIVE FIXTURE. The LLM-natural mistake: a two-binder loop
|
||||
; (acc, i) but the author writes `(recur (app + acc i))` forgetting
|
||||
; to also advance `i` — passes 1 arg for 2 binders.
|
||||
; Expected: `ail check` exits 1 with code recur-arity-mismatch.
|
||||
(module loop_recur_3b_recur_arity
|
||||
(fn sum_to
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (acc (con Int) 0) (i (con Int) 1)
|
||||
(if (app > i n)
|
||||
acc
|
||||
(recur (app + acc i))))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app sum_to 10)))))
|
||||
@@ -0,0 +1,17 @@
|
||||
; NEGATIVE FIXTURE. The LLM-natural mistake: the author intends to
|
||||
; recur with the updated Int accumulator but accidentally passes a
|
||||
; Bool (the comparison result) into the first binder position.
|
||||
; Expected: `ail check` exits 1 with code recur-type-mismatch.
|
||||
(module loop_recur_3c_recur_type
|
||||
(fn count_down
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params start)
|
||||
(body
|
||||
(loop (i (con Int) start)
|
||||
(if (app == i 0)
|
||||
i
|
||||
(recur (app > i 0))))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app count_down 5)))))
|
||||
@@ -0,0 +1,18 @@
|
||||
; NEGATIVE FIXTURE. The LLM-natural mistake: the author treats
|
||||
; `recur` like a value-returning recursive call and multiplies its
|
||||
; result, computing factorial as `n * recur(n-1)`. recur is an
|
||||
; argument to *, not in tail position.
|
||||
; Expected: `ail check` exits 1 with code recur-not-in-tail-position.
|
||||
(module loop_recur_3d_recur_not_tail
|
||||
(fn factorial
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (i (con Int) n)
|
||||
(if (app == i 1)
|
||||
1
|
||||
(app * i (recur (app - i 1)))))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app factorial 5)))))
|
||||
@@ -0,0 +1,23 @@
|
||||
; NEGATIVE FIXTURE. The LLM-natural mistake: inside the loop body
|
||||
; the author builds a closure that closes over the loop binder
|
||||
; `acc` (a `\delta. acc + delta` adder) and applies it. Loop
|
||||
; binders are alloca-resident and may not be captured.
|
||||
; Expected: `ail check` exits 1 with code loop-binder-captured-by-lambda.
|
||||
(module loop_recur_3e_binder_captured
|
||||
(fn apply_int
|
||||
(type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int))))
|
||||
(params f x)
|
||||
(body (app f x)))
|
||||
(fn sum_to
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params n)
|
||||
(body
|
||||
(loop (acc (con Int) 0) (i (con Int) 1)
|
||||
(if (app > i n)
|
||||
acc
|
||||
(recur (app apply_int (lam (params (typed d (con Int))) (ret (con Int)) (body (app + acc d))) i)
|
||||
(app + i 1))))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app sum_to 10)))))
|
||||
@@ -0,0 +1,24 @@
|
||||
; Two things at once:
|
||||
; (1) `loop` used as a sub-expression in argument position — the
|
||||
; (loop ...) result of `gcd` is passed straight into another
|
||||
; call (`app print (app + (app gcd 48 18) (app gcd 1071 462)))`),
|
||||
; not used as a whole fn body. Expected stdout: 27 (gcd(48,18)=6
|
||||
; plus gcd(1071,462)=21).
|
||||
; (2) Euclid's algorithm is a textbook real iterative algorithm an
|
||||
; LLM author reaches for `loop`/`recur` to write; single-binder
|
||||
; pair? no — two binders (a, b), recur swaps/reduces.
|
||||
(module loop_recur_4_gcd_value_pos
|
||||
(fn gcd
|
||||
(doc "Euclidean gcd via loop/recur. Two binders a,b; recur in the tail of the else branch.")
|
||||
(type (fn-type (params (con Int) (con Int)) (ret (con Int))))
|
||||
(params x y)
|
||||
(body
|
||||
(loop (a (con Int) x) (b (con Int) y)
|
||||
(if (app == b 0)
|
||||
a
|
||||
(recur b (app % a b))))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(app print (app + (app gcd 48 18) (app gcd 1071 462))))))
|
||||
@@ -0,0 +1,19 @@
|
||||
; NO-TERMINATION BOUNDARY fixture. An LLM-natural shape: a "tick"
|
||||
; event loop that never exits (no non-recur branch). Per the
|
||||
; loop-recur spec this MUST `ail check` AND `ail build` cleanly —
|
||||
; AILang makes no termination claim. BUILD-ONLY: by design this
|
||||
; binary never returns, so it is never executed.
|
||||
(module loop_recur_5_event_loop_noterm
|
||||
(fn run_forever
|
||||
(doc "Infinite event loop. Two binders: tick counter and a running parity flag. No branch ever exits via a non-recur term.")
|
||||
(type (fn-type (params) (ret (con Int))))
|
||||
(params)
|
||||
(body
|
||||
(loop (tick (con Int) 0) (parity (con Int) 0)
|
||||
(if (app == (app % tick 2) 0)
|
||||
(recur (app + tick 1) 1)
|
||||
(recur (app + tick 1) 0)))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app run_forever)))))
|
||||
@@ -0,0 +1,21 @@
|
||||
; NO-TERMINATION BOUNDARY fixture (loop-axis-covering sibling of
|
||||
; loop_recur_5_event_loop_noterm.ail, which surfaced the orthogonal
|
||||
; niladic-call obstacle). An LLM-natural shape: a "tick" event loop
|
||||
; seeded from a start value that never exits (no non-recur branch).
|
||||
; Per the loop-recur spec this MUST `ail check` AND `ail build`
|
||||
; cleanly — AILang makes no termination claim. BUILD-ONLY: by
|
||||
; design this binary never returns, so it is never executed.
|
||||
(module loop_recur_5b_event_loop_noterm
|
||||
(fn run_forever
|
||||
(doc "Infinite event loop. Two binders: tick counter and a running parity flag. No branch ever exits via a non-recur term.")
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(params start)
|
||||
(body
|
||||
(loop (tick (con Int) start) (parity (con Int) 0)
|
||||
(if (app == (app % tick 2) 0)
|
||||
(recur (app + tick 1) 1)
|
||||
(recur (app + tick 1) 0)))))
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body (app print (app run_forever 0)))))
|
||||
Reference in New Issue
Block a user