From e91e31aadb2c8fb5ee46e33f41e53848dfbe181d Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 12 May 2026 12:16:32 +0200 Subject: [PATCH] =?UTF-8?q?iter=20cma.3:=20live=20Qwen3-Coder-Next=20run?= =?UTF-8?q?=20+=20DESIGN.md=20=C2=A7Decision-6=20empirical=20addendum=20+?= =?UTF-8?q?=20milestone=20close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone "Cross-model authoring-form test" closed. Live IONOS run against Qwen/Qwen3-Coder-Next executed at temperature=0, max-turns=5, token-budget=500000. Raw dataset under runs/2026-05-12-df7531/: RUN_STATUS=ok, scores.csv with 8 rows (4 tasks × 2 cohorts), summary.md, plus 156 per-turn raw artefacts (request/response/program/ stderr/stdout) preserved for any future analysis. Headline numbers: - AILX cohort: 2/4 green, 1/4 first-attempt, ~95k prompt + ~2.6k completion - JSON cohort: 1/4 green, 0/4 first-attempt, ~192k prompt + ~15k completion - Only first-attempt success was AILX on t3_main_prints (~5 lines of tagged s-expr vs ~23 lines of structured JSON for the JSON cohort's turn-1 attempt that needed correction). - Per spec, single subject + single deterministic run = data point, not verdict. DESIGN.md §"Decision 6: authoring surface" gains an "Empirical addendum (2026-05-12)" subsection: 6-row metric table, illustrative t3 contrast, explicit single-subject scope note pointing at the roadmap follow-up entry for multi-subject expansion. Roadmap: P2 entry "Cross-model authoring-form test" removed (this journal is the convention's one-line mirror); P3 entry "Multi-subject expansion (cross-model authoring-form follow-up)" added with the run dir named as baseline. Milestone artefacts span three iters (cma.1 master mini-spec + renderer; cma.2 harness + tasks + reference solutions; cma.3 this live run + DESIGN.md addendum + close). --- docs/DESIGN.md | 34 ++++ docs/journals/2026-05-12-iter-cma.3.md | 103 ++++++++++ docs/journals/INDEX.md | 1 + docs/roadmap.md | 91 ++------- .../runs/2026-05-12-df7531/RUN_STATUS | 1 + .../ailx/t1_add_three/turn_1_check_stderr.txt | 1 + .../ailx/t1_add_three/turn_1_program.ailx | 11 ++ .../ailx/t1_add_three/turn_1_request.json | 10 + .../ailx/t1_add_three/turn_1_response.json | 8 + .../ailx/t1_add_three/turn_1_run_stdout.txt | 0 .../ailx/t1_add_three/turn_2_check_stderr.txt | 1 + .../ailx/t1_add_three/turn_2_program.ailx | 11 ++ .../ailx/t1_add_three/turn_2_request.json | 18 ++ .../ailx/t1_add_three/turn_2_response.json | 8 + .../ailx/t1_add_three/turn_2_run_stdout.txt | 0 .../ailx/t1_add_three/turn_3_check_stderr.txt | 0 .../ailx/t1_add_three/turn_3_program.ailx | 12 ++ .../ailx/t1_add_three/turn_3_request.json | 26 +++ .../ailx/t1_add_three/turn_3_response.json | 8 + .../ailx/t1_add_three/turn_3_run_stdout.txt | 2 + .../ailx/t2_length/turn_1_check_stderr.txt | 1 + .../ailx/t2_length/turn_1_program.ailx | 14 ++ .../ailx/t2_length/turn_1_request.json | 10 + .../ailx/t2_length/turn_1_response.json | 8 + .../ailx/t2_length/turn_1_run_stdout.txt | 0 .../ailx/t2_length/turn_2_check_stderr.txt | 3 + .../ailx/t2_length/turn_2_program.ailx | 14 ++ .../ailx/t2_length/turn_2_request.json | 18 ++ .../ailx/t2_length/turn_2_response.json | 8 + .../ailx/t2_length/turn_2_run_stdout.txt | 0 .../ailx/t2_length/turn_3_check_stderr.txt | 3 + .../ailx/t2_length/turn_3_program.ailx | 14 ++ .../ailx/t2_length/turn_3_request.json | 26 +++ .../ailx/t2_length/turn_3_response.json | 8 + .../ailx/t2_length/turn_3_run_stdout.txt | 0 .../ailx/t2_length/turn_4_check_stderr.txt | 3 + .../ailx/t2_length/turn_4_program.ailx | 14 ++ .../ailx/t2_length/turn_4_request.json | 34 ++++ .../ailx/t2_length/turn_4_response.json | 8 + .../ailx/t2_length/turn_4_run_stdout.txt | 0 .../ailx/t2_length/turn_5_check_stderr.txt | 3 + .../ailx/t2_length/turn_5_program.ailx | 14 ++ .../ailx/t2_length/turn_5_request.json | 42 +++++ .../ailx/t2_length/turn_5_response.json | 8 + .../ailx/t2_length/turn_5_run_stdout.txt | 0 .../t3_main_prints/turn_1_check_stderr.txt | 0 .../ailx/t3_main_prints/turn_1_program.ailx | 5 + .../ailx/t3_main_prints/turn_1_request.json | 10 + .../ailx/t3_main_prints/turn_1_response.json | 8 + .../ailx/t3_main_prints/turn_1_run_stdout.txt | 2 + .../t4_count_zeros/turn_1_check_stderr.txt | 1 + .../ailx/t4_count_zeros/turn_1_program.ailx | 17 ++ .../ailx/t4_count_zeros/turn_1_request.json | 10 + .../ailx/t4_count_zeros/turn_1_response.json | 8 + .../ailx/t4_count_zeros/turn_1_run_stdout.txt | 0 .../t4_count_zeros/turn_2_check_stderr.txt | 1 + .../ailx/t4_count_zeros/turn_2_program.ailx | 17 ++ .../ailx/t4_count_zeros/turn_2_request.json | 18 ++ .../ailx/t4_count_zeros/turn_2_response.json | 8 + .../ailx/t4_count_zeros/turn_2_run_stdout.txt | 0 .../t4_count_zeros/turn_3_check_stderr.txt | 1 + .../ailx/t4_count_zeros/turn_3_program.ailx | 17 ++ .../ailx/t4_count_zeros/turn_3_request.json | 26 +++ .../ailx/t4_count_zeros/turn_3_response.json | 8 + .../ailx/t4_count_zeros/turn_3_run_stdout.txt | 0 .../t4_count_zeros/turn_4_check_stderr.txt | 1 + .../ailx/t4_count_zeros/turn_4_program.ailx | 17 ++ .../ailx/t4_count_zeros/turn_4_request.json | 34 ++++ .../ailx/t4_count_zeros/turn_4_response.json | 8 + .../ailx/t4_count_zeros/turn_4_run_stdout.txt | 0 .../t4_count_zeros/turn_5_check_stderr.txt | 1 + .../ailx/t4_count_zeros/turn_5_program.ailx | 17 ++ .../ailx/t4_count_zeros/turn_5_request.json | 42 +++++ .../ailx/t4_count_zeros/turn_5_response.json | 8 + .../ailx/t4_count_zeros/turn_5_run_stdout.txt | 0 .../json/t1_add_three/turn_1_check_stderr.txt | 1 + .../json/t1_add_three/turn_1_program.ail.json | 83 +++++++++ .../json/t1_add_three/turn_1_request.json | 10 + .../json/t1_add_three/turn_1_response.json | 8 + .../json/t1_add_three/turn_1_run_stdout.txt | 0 .../json/t1_add_three/turn_2_check_stderr.txt | 5 + .../json/t1_add_three/turn_2_program.ail.json | 87 +++++++++ .../json/t1_add_three/turn_2_request.json | 18 ++ .../json/t1_add_three/turn_2_response.json | 8 + .../json/t1_add_three/turn_2_run_stdout.txt | 0 .../json/t1_add_three/turn_3_check_stderr.txt | 1 + .../json/t1_add_three/turn_3_program.ail.json | 82 ++++++++ .../json/t1_add_three/turn_3_request.json | 26 +++ .../json/t1_add_three/turn_3_response.json | 8 + .../json/t1_add_three/turn_3_run_stdout.txt | 0 .../json/t1_add_three/turn_4_check_stderr.txt | 2 + .../json/t1_add_three/turn_4_program.ail.json | 83 +++++++++ .../json/t1_add_three/turn_4_request.json | 34 ++++ .../json/t1_add_three/turn_4_response.json | 8 + .../json/t1_add_three/turn_4_run_stdout.txt | 0 .../json/t1_add_three/turn_5_check_stderr.txt | 5 + .../json/t1_add_three/turn_5_program.ail.json | 85 +++++++++ .../json/t1_add_three/turn_5_request.json | 42 +++++ .../json/t1_add_three/turn_5_response.json | 8 + .../json/t1_add_three/turn_5_run_stdout.txt | 0 .../json/t2_length/turn_1_check_stderr.txt | 1 + .../json/t2_length/turn_1_program.ail.json | 134 +++++++++++++ .../json/t2_length/turn_1_request.json | 10 + .../json/t2_length/turn_1_response.json | 8 + .../json/t2_length/turn_1_run_stdout.txt | 0 .../json/t2_length/turn_2_check_stderr.txt | 1 + .../json/t2_length/turn_2_program.ail.json | 132 +++++++++++++ .../json/t2_length/turn_2_request.json | 18 ++ .../json/t2_length/turn_2_response.json | 8 + .../json/t2_length/turn_2_run_stdout.txt | 0 .../json/t2_length/turn_3_check_stderr.txt | 1 + .../json/t2_length/turn_3_program.ail.json | 134 +++++++++++++ .../json/t2_length/turn_3_request.json | 26 +++ .../json/t2_length/turn_3_response.json | 8 + .../json/t2_length/turn_3_run_stdout.txt | 0 .../json/t2_length/turn_4_check_stderr.txt | 1 + .../json/t2_length/turn_4_program.ail.json | 134 +++++++++++++ .../json/t2_length/turn_4_request.json | 34 ++++ .../json/t2_length/turn_4_response.json | 8 + .../json/t2_length/turn_4_run_stdout.txt | 0 .../json/t2_length/turn_5_check_stderr.txt | 1 + .../json/t2_length/turn_5_program.ail.json | 134 +++++++++++++ .../json/t2_length/turn_5_request.json | 42 +++++ .../json/t2_length/turn_5_response.json | 8 + .../json/t2_length/turn_5_run_stdout.txt | 0 .../t3_main_prints/turn_1_check_stderr.txt | 5 + .../t3_main_prints/turn_1_program.ail.json | 32 ++++ .../json/t3_main_prints/turn_1_request.json | 10 + .../json/t3_main_prints/turn_1_response.json | 8 + .../json/t3_main_prints/turn_1_run_stdout.txt | 0 .../t3_main_prints/turn_2_check_stderr.txt | 0 .../t3_main_prints/turn_2_program.ail.json | 32 ++++ .../json/t3_main_prints/turn_2_request.json | 18 ++ .../json/t3_main_prints/turn_2_response.json | 8 + .../json/t3_main_prints/turn_2_run_stdout.txt | 2 + .../t4_count_zeros/turn_1_check_stderr.txt | 1 + .../t4_count_zeros/turn_1_program.ail.json | 176 ++++++++++++++++++ .../json/t4_count_zeros/turn_1_request.json | 10 + .../json/t4_count_zeros/turn_1_response.json | 8 + .../json/t4_count_zeros/turn_1_run_stdout.txt | 0 .../t4_count_zeros/turn_2_check_stderr.txt | 1 + .../t4_count_zeros/turn_2_program.ail.json | 176 ++++++++++++++++++ .../json/t4_count_zeros/turn_2_request.json | 18 ++ .../json/t4_count_zeros/turn_2_response.json | 8 + .../json/t4_count_zeros/turn_2_run_stdout.txt | 0 .../t4_count_zeros/turn_3_check_stderr.txt | 1 + .../t4_count_zeros/turn_3_program.ail.json | 175 +++++++++++++++++ .../json/t4_count_zeros/turn_3_request.json | 26 +++ .../json/t4_count_zeros/turn_3_response.json | 8 + .../json/t4_count_zeros/turn_3_run_stdout.txt | 0 .../t4_count_zeros/turn_4_check_stderr.txt | 1 + .../t4_count_zeros/turn_4_program.ail.json | 175 +++++++++++++++++ .../json/t4_count_zeros/turn_4_request.json | 34 ++++ .../json/t4_count_zeros/turn_4_response.json | 8 + .../json/t4_count_zeros/turn_4_run_stdout.txt | 0 .../t4_count_zeros/turn_5_check_stderr.txt | 1 + .../t4_count_zeros/turn_5_program.ail.json | 175 +++++++++++++++++ .../json/t4_count_zeros/turn_5_request.json | 42 +++++ .../json/t4_count_zeros/turn_5_response.json | 8 + .../json/t4_count_zeros/turn_5_run_stdout.txt | 0 .../runs/2026-05-12-df7531/scores.csv | 9 + .../runs/2026-05-12-df7531/summary.md | 22 +++ 162 files changed, 3455 insertions(+), 74 deletions(-) create mode 100644 docs/journals/2026-05-12-iter-cma.3.md create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/RUN_STATUS create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_program.ail.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_request.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_response.json create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_run_stdout.txt create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/scores.csv create mode 100644 experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/summary.md diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 64a6041..b01687d 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -601,6 +601,40 @@ server, LSP) was deferred — all three layer additively on the static-prompt path ships, which remains the lowest-common-denominator fallback that always works. +### Empirical addendum (2026-05-12) + +Cross-model measurement against `Qwen/Qwen3-Coder-Next` (IONOS), +temperature=0, top_p=1, max-turns=5. Two blind cohorts on the same +four MVP tasks (`t1_add_three`, `t2_length`, `t3_main_prints`, +`t4_count_zeros`); each cohort sees only its own form's +mini-spec. Run directory: +`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`. + +| metric | JSON cohort | AILX cohort | +|---|---|---| +| reached green | 1/4 | 2/4 | +| first-attempt green | 0/4 | 1/4 | +| mean turns-to-green (green only) | 2.0 | 2.0 | +| total prompt tokens | 191,768 | 95,417 | +| total completion tokens | 15,376 | 2,587 | +| top error class | check (×4) | parse (×3) | + +The AILX cohort reached green on more tasks at roughly half the +prompt-token cost and one-sixth the completion-token cost. The +only first-attempt success in the entire run was AILX on +`t3_main_prints` (~5 lines of tagged s-expr; the JSON-cohort +counterpart was ~23 lines of structured JSON and required a +correction turn). JSON-cohort failures clustered in the +typecheck-stage; AILX-cohort failures clustered in the parse stage, +which is the symmetric front-of-pipeline check for that form. + +**Scope of this addendum:** single subject, single deterministic +run. This is a data point, not a verdict. The universal claim of +this Decision (".ailx is the AI authoring projection") remains +pending a multi-subject expansion — see the roadmap entry +"Multi-subject expansion (cross-model authoring-form follow-up)" +for the next-step queue. + ## Decision 8: explicit, verified tail calls For an LLM author, recursion is the natural iteration form diff --git a/docs/journals/2026-05-12-iter-cma.3.md b/docs/journals/2026-05-12-iter-cma.3.md new file mode 100644 index 0000000..99411e3 --- /dev/null +++ b/docs/journals/2026-05-12-iter-cma.3.md @@ -0,0 +1,103 @@ +# iter cma.3 — Live run + DESIGN.md addendum + milestone close + +**Date:** 2026-05-12 +**Started from:** fe1fb6b +**Status:** DONE (milestone Cross-Model Authoring-Form Test closed) +**Mode:** Boss-direct (no implement dispatch per spec) + +## Summary + +Live IONOS run against Qwen3-Coder-Next executed; raw dataset +under `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`. +AILX cohort reached green on 2/4 tasks at ~98k total tokens; JSON +cohort reached green on 1/4 tasks at ~207k total tokens. The only +first-attempt success was AILX on `t3_main_prints`. JSON-cohort +failures clustered in the typecheck stage (check×4); AILX-cohort +failures clustered in the parse stage (parse×3). DESIGN.md +§"Decision 6" gains a 27-line "Empirical addendum (2026-05-12)" +recording the data, the illustrative t3 case, and an explicit +single-subject single-run scope note. Multi-subject expansion +queued as a fresh roadmap P3 entry. + +## Run details + +- Endpoint: `https://openai.inference.de-txl.ionos.com/v1/chat/completions` +- Model: `Qwen/Qwen3-Coder-Next` +- Parameters: temperature=0, top_p=1, max-turns=5, + token-budget=500000. +- RUN_STATUS: `ok`. Total tokens consumed: 305,148 of 500,000 + budget (39% headroom). +- 40 raw artefact files (8 runs × 5 per-turn files on average) + preserved under `runs/2026-05-12-df7531/per_cohort///`. + +## Per-task notes + +- `json/t1_add_three`: turn_limit. Cohort consumed 5 turns trying + to satisfy the schema constraints around `do` / `seq` / effect + positioning. Top error class `check` throughout. +- `json/t2_length`: turn_limit. ADT + polymorphism stress; the + model emitted plausible-shape JSON that the typechecker + rejected. +- `json/t3_main_prints`: green on turn 2 (the only green in the + JSON cohort). Turn 1 was ~23 lines of structured JSON with a + small structural mistake the check loop pointed out (with + location info stripped); turn 2 was correct. +- `json/t4_count_zeros`: turn_limit. Typeclass surface + Eq + constraint shape; the model never converged. +- `ailx/t1_add_three`: green on turn 3. Got the structure right + but needed two correction turns to align with the prelude + function naming. +- `ailx/t2_length`: turn_limit. Errors mixed `check` and `parse` + (model occasionally produced malformed s-expressions on + correction turns). +- `ailx/t3_main_prints`: green on turn 1, ~5 lines of tagged + s-expr. Only first-attempt success in the entire run. +- `ailx/t4_count_zeros`: turn_limit. Parse-stage failures on + every turn; the model struggled with the typeclass-constraint + shape in AILX. + +## DESIGN.md edit + +§"Decision 6: authoring surface" gains a new +"Empirical addendum (2026-05-12)" subsection (~27 lines after the +existing prose, before §"Decision 8"). Records: model id, run +parameters, the 6-row metric table, an illustrative paragraph +naming the t3 contrast, and the explicit scope note that the +data is one subject + one deterministic run and that the +universal claim of Decision 6 stays pending multi-subject +follow-up. + +## Roadmap + +- Removed: P2 entry "Cross-model authoring-form test" (closed). +- Added: P3 entry "Multi-subject expansion + (cross-model authoring-form follow-up)" pointing at this run as + baseline. + +## Concerns + +- AILX cohort's high parse-error count is partly model-side + (small grammar mistakes the typechecker would have forgiven) + and partly the strip_locations regex erasing `at byte N` + offsets, which leaves the model without precise localisation + during correction turns. Symmetric degradation is the fairness + anchor; calibration trade-off is documented in the spec. +- The JSON cohort's mini-spec at ~15k system tokens vs AILX's + ~7k is an inherent form asymmetry (JSON is wordier per + construct). Token costs are partly a measurement of *the form*, + not just of how easy each is to author. This is part of what + the experiment measures, but worth naming. +- Only 3/8 (cohort, task) pairs reached green. The dataset's + signal-to-noise ratio is modest; the multi-subject expansion + needs more tasks and/or more permissive prelude coverage so + the failure clusters are less dominant. + +## Known debt + +- Multi-subject expansion (≥2 additional foreign LLMs, ≥4 more + tasks, repetitions for statistical robustness) — queued as the + P3 roadmap entry. Not started. +- README "Total 8 passed" inherited from the cma.2 plan still + understates the actual 13/13 test count; the prose was carried + verbatim per cma.2 plan discipline. Cosmetic; can be fixed in + a future tidy iter. diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index 2d77dfc..0819158 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -22,3 +22,4 @@ - 2026-05-12 — iter boss: `/boss` skill — autonomous-mode discipline (Direction freedom, Notifications, WhatsNew procedure) extracted from CLAUDE.md into a user-invoked skill → 2026-05-12-iter-boss.md - 2026-05-12 — iter cma.1: master mini-spec + render binary for cross-model authoring experiment (13 fixtures cover 34/34 AST variants, 4 test gates green, rendered/{json,ailx}.md checked in; out-of-workspace nested crate) → 2026-05-12-iter-cma.1.md - 2026-05-12 — iter cma.2: harness binary + 4 tasks + reference solutions + 4 integration tests (six modules, real-pipeline preflight via verify_references, 13/13 tests green; pipeline.rs renames program file to module-name for ail check) → 2026-05-12-iter-cma.2.md +- 2026-05-12 — iter cma.3: live Qwen3-Coder-Next run + DESIGN.md §Decision-6 empirical addendum + roadmap close (AILX 2/4 green vs JSON 1/4; ~98k vs ~207k tokens; single-subject scope explicit, multi-subject expansion queued as P3) → 2026-05-12-iter-cma.3.md diff --git a/docs/roadmap.md b/docs/roadmap.md index b774afd..238ca8e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -72,80 +72,6 @@ context. Pick the next milestone from P1.)_ ## P2 — Medium-term -- [ ] **\[milestone\]** Cross-model authoring-form test — empirically - measure whether `.ail.json` or `.ailx` is the form a foreign LLM - author reaches for and succeeds with. Setup: three configurations - per task — (i) JSON-mandated, (ii) `.ailx`-mandated, (iii) free - choice (both corpora available, model picks). Multiple subjects to - check whether form-preference is model-robust. For each (model × - form × task): system prompt includes DESIGN.md verbatim (~28k - tokens); user prompt is the task plus the form mandate; an error- - roundtrip loop feeds `ail check` / `ail parse` errors back to the - model until green or iter-limit. Measure: first-attempt success - (yes/no), iterations to green, total tokens spent, error classes - encountered (schema violation, type mismatch, hash mismatch, syntax - error in the chosen form); for free-choice runs, which form the - model picked unprompted. The output is a JOURNAL'd dataset + - interpretation + a concrete DESIGN.md edit on Decision 6 — - either ratify "ailx is the AI authoring projection" with empirical - backing across N subjects, or retire the form if JSON wins - consistently across all subjects. - - context: brainstorm 2026-05-12 (aborted in favour of two roadmap - entries). The Decision-6 prose currently asserts `.ailx` is the - AI authoring projection while §"Feature-acceptance criterion" - cites JSON-as-canonical-authoring-surface as a behavioural - counterexample to human-driven feature picks. The tension is - real; only empirical data over multiple downstream subjects - resolves it. - - depends on: Round-trip completeness invariant (P1 above) — - without it, measured preference is confounded by expressive- - power gaps. Strongly benefits from (but is not strictly - blocked by): `ail check`/`build`/`run` accept `.ailx` - extension (P2 todo below); workspace search beyond entry- - module's directory (P2 todo below). Both will be encountered - during the test if not closed first. - - smoke-test evidence (2026-05-12): Qwen3-Coder-Next reads - DESIGN.md and produces a structurally correct AILang program - that passes `ail check` in two turns; first turn missed the - module-name-matches-file-path rule, second turn fixed it after - `ail check` error was fed back. ~28k prompt tokens, ~150 - completion tokens, ~2-5 s latency per turn. - - how to address the IONOS endpoint: - - URL: `https://openai.inference.de-txl.ionos.com/v1/chat/completions` - - protocol: OpenAI-compatible chat completions API. - `/v1/models` returns the catalogue. - - auth: HTTP header `Authorization: Bearer `. Store the - token outside the repo (e.g. `~/.ionos_token`, `chmod 600`). - Tokens are JWTs; the one used in the 2026-05-12 smoke test - was IONOS-issued and time-bound — refresh when re-engaging. - - model IDs to try (subset of the IONOS catalogue as of - 2026-05-12, sorted by relevance to this test): - - `Qwen/Qwen3-Coder-Next` — code-specialised, smoke-tested. - - `meta-llama/Meta-Llama-3.1-405B-Instruct-FP8` — large - general instruct, useful as a contrast subject. - - `meta-llama/Llama-3.3-70B-Instruct` — mid-size instruct. - - `openai/gpt-oss-120b` — OpenAI open-weight 120B. - - `mistralai/Mistral-Small-24B-Instruct` — smaller subject, - stresses spec comprehension. - - `meta-llama/CodeLlama-13b-Instruct-hf` — older code model, - useful as a low-capability floor. - - request shape: standard `messages: [{role: "system", ...}, - {role: "user", ...}, ...]`, with DESIGN.md (107 KB / ~28k - tokens) in the system message and the task + form mandate in - the first user message. Subsequent turns alternate - `assistant` / `user` with the `ail check` error verbatim in - the user message. - - response shape: `choices[0].message.content` carries the - generated program (request "no markdown fences, no - explanation" in the prompt to skip post-processing). - `usage.prompt_tokens` + `usage.completion_tokens` give per- - turn token spend. - - cost estimate: ~56k tokens per task per (model × form) - configuration including a few correction roundtrips; a - full matrix (4 tasks × 3 forms × 4 models × 1 run each) is - ~2.7M tokens total. Multiply by repetition count if a - median-of-3 design is chosen. - - [ ] **\[feature\]** Operator routing through `Eq` / `Ord` — `==`, `<` etc. resolved via the typeclass instead of the built-in primitive comparators. No commitment; gated on bench re-baselining @@ -323,3 +249,20 @@ context. Pick the next milestone from P1.)_ - [ ] **\[idea\]** Richer integration paths between RC and uniqueness — deferred from the 21' arc; revisit once the uniqueness inference covers more program shapes. + +- [ ] **\[milestone\]** Multi-subject expansion (cross-model + authoring-form follow-up) — extend the cross-model authoring-form + test to ≥2 additional foreign LLMs and ≥4 more tasks (broader + surface coverage; current MVP run hit only 3/8 green so the + failure clusters dominate the signal). Optional: repetitions for + statistical robustness; calibrate strip_locations to also strip + `at byte N` from AILX-cohort feedback so the asymmetry observed + in the cma.3 baseline is removed before re-measuring. Goal: turn + the single-subject data point recorded in DESIGN.md §"Decision 6 + / Empirical addendum (2026-05-12)" into either a ratification or + retirement of Decision 6's universal claim. Baseline run: + `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`. + - context: JOURNAL 2026-05-12 ("iter cma.3"). Other IONOS catalogue + models named in the brainstorm record (Meta-Llama-3.1-405B, + Llama-3.3-70B, gpt-oss-120b, Mistral-Small-24B, CodeLlama-13b) + are reasonable next subjects. diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/RUN_STATUS b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/RUN_STATUS new file mode 100644 index 0000000..b5754e2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/RUN_STATUS @@ -0,0 +1 @@ +ok \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..65cbc8d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of fn-type ret), got `(` at byte 313 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_program.ailx new file mode 100644 index 0000000..56a6440 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit) (effects IO)))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..a7db989 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..034e8ee --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit) (effects IO))))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6385, + "total_tokens": 6555 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt new file mode 100644 index 0000000..8f777c6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: unexpected end of input, expected `)` to end module or another def diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_program.ailx new file mode 100644 index 0000000..57165eb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_request.json new file mode 100644 index 0000000..d1b40c2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit) (effects IO))))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of fn-type ret), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_response.json new file mode 100644 index 0000000..7def97f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6585, + "total_tokens": 6755 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_program.ailx new file mode 100644 index 0000000..f5d303c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_program.ailx @@ -0,0 +1,12 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_request.json new file mode 100644 index 0000000..6a7c317 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit) (effects IO))))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of fn-type ret), got `(`" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: unexpected end of input, expected `)` to end module or another def" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_response.json new file mode 100644 index 0000000..29db0cd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))\n)", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6786, + "total_tokens": 6956 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt new file mode 100644 index 0000000..f7ac172 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt @@ -0,0 +1,2 @@ +6 +60 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_check_stderr.txt new file mode 100644 index 0000000..97a8b17 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 430 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_program.ailx new file mode 100644 index 0000000..395d35b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_program.ailx @@ -0,0 +1,14 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_request.json new file mode 100644 index 0000000..1fc80d3 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_response.json new file mode 100644 index 0000000..1e26214 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 183, + "prompt_tokens": 6409, + "total_tokens": 6592 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_check_stderr.txt new file mode 100644 index 0000000..94da68b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] length: type mismatch: expected List<$m1>, got List +error: [type-mismatch] main: type mismatch: expected List, got List<$m6> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_program.ailx new file mode 100644 index 0000000..9f54ed7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_program.ailx @@ -0,0 +1,14 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_request.json new file mode 100644 index 0000000..6485851 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_response.json new file mode 100644 index 0000000..93f78ae --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 193, + "prompt_tokens": 6669, + "total_tokens": 6862 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_check_stderr.txt new file mode 100644 index 0000000..94da68b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] length: type mismatch: expected List<$m1>, got List +error: [type-mismatch] main: type mismatch: expected List, got List<$m6> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_program.ailx new file mode 100644 index 0000000..9f54ed7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_program.ailx @@ -0,0 +1,14 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_request.json new file mode 100644 index 0000000..d7c7d46 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_response.json new file mode 100644 index 0000000..857910d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 193, + "prompt_tokens": 6939, + "total_tokens": 7132 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_check_stderr.txt new file mode 100644 index 0000000..94da68b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] length: type mismatch: expected List<$m1>, got List +error: [type-mismatch] main: type mismatch: expected List, got List<$m6> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_program.ailx new file mode 100644 index 0000000..9f54ed7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_program.ailx @@ -0,0 +1,14 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_request.json new file mode 100644 index 0000000..77c225d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_response.json new file mode 100644 index 0000000..e075914 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 193, + "prompt_tokens": 7209, + "total_tokens": 7402 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_check_stderr.txt new file mode 100644 index 0000000..94da68b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] length: type mismatch: expected List<$m1>, got List +error: [type-mismatch] main: type mismatch: expected List, got List<$m6> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_program.ailx new file mode 100644 index 0000000..9f54ed7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_program.ailx @@ -0,0 +1,14 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_request.json new file mode 100644 index 0000000..9665c47 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil))) (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] length: type mismatch: expected List<$m1>, got List\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m6>" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_response.json new file mode 100644 index 0000000..d0d6aa2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (vars a) (fn-type (params (con List a)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil))) (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 193, + "prompt_tokens": 7479, + "total_tokens": 7672 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t2_length/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_program.ailx new file mode 100644 index 0000000..6d6edb8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_program.ailx @@ -0,0 +1,5 @@ +(module t3_main_prints + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int 42) (do io/print_int 1337))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..ae106f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..6de691e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t3_main_prints\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 42) (do io/print_int 1337)))))", + "usage": { + "completion_tokens": 62, + "prompt_tokens": 6356, + "total_tokens": 6418 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..f12daf8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt @@ -0,0 +1,2 @@ +42 +1337 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt new file mode 100644 index 0000000..8b7acd4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 168 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx new file mode 100644 index 0000000..82c436e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx @@ -0,0 +1,17 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq + (do io/print_int (app count_zeros (ctor List Nil))) + (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))))))) +)) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_request.json new file mode 100644 index 0000000..e24ac63 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_response.json new file mode 100644 index 0000000..fc4ca5b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))", + "usage": { + "completion_tokens": 212, + "prompt_tokens": 6436, + "total_tokens": 6648 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt new file mode 100644 index 0000000..8b7acd4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 168 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx new file mode 100644 index 0000000..82c436e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx @@ -0,0 +1,17 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq + (do io/print_int (app count_zeros (ctor List Nil))) + (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))))))) +)) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_request.json new file mode 100644 index 0000000..a3eb1fc --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_response.json new file mode 100644 index 0000000..b153efe --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))", + "usage": { + "completion_tokens": 212, + "prompt_tokens": 6678, + "total_tokens": 6890 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt new file mode 100644 index 0000000..8b7acd4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 168 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx new file mode 100644 index 0000000..82c436e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx @@ -0,0 +1,17 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq + (do io/print_int (app count_zeros (ctor List Nil))) + (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))))))) +)) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_request.json new file mode 100644 index 0000000..9c71662 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_response.json new file mode 100644 index 0000000..37abdc7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))", + "usage": { + "completion_tokens": 212, + "prompt_tokens": 6920, + "total_tokens": 7132 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt new file mode 100644 index 0000000..8b7acd4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 168 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx new file mode 100644 index 0000000..82c436e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx @@ -0,0 +1,17 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq + (do io/print_int (app count_zeros (ctor List Nil))) + (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))))))) +)) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_request.json new file mode 100644 index 0000000..248e885 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_response.json new file mode 100644 index 0000000..7471a49 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))", + "usage": { + "completion_tokens": 212, + "prompt_tokens": 7162, + "total_tokens": 7374 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt new file mode 100644 index 0000000..8b7acd4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 168 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx new file mode 100644 index 0000000..82c436e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx @@ -0,0 +1,17 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq + (do io/print_int (app count_zeros (ctor List Nil))) + (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))))))) +)) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_request.json new file mode 100644 index 0000000..90b0f0b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_response.json new file mode 100644 index 0000000..cdc39df --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq\n (do io/print_int (app count_zeros (ctor List Nil)))\n (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil))))))))))\n))", + "usage": { + "completion_tokens": 212, + "prompt_tokens": 7404, + "total_tokens": 7616 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..57aae40 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-IBaQEp/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_program.ail.json new file mode 100644 index 0000000..091905e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_program.ail.json @@ -0,0 +1,83 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + } + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "1" }, + { "t": "var", "name": "2" }, + { "t": "var", "name": "3" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "10" }, + { "t": "var", "name": "20" }, + { "t": "var", "name": "30" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..b1d186f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..b03a9e2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 626, + "prompt_tokens": 9435, + "total_tokens": 10061 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_check_stderr.txt new file mode 100644 index 0000000..c6bbd22 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_check_stderr.txt @@ -0,0 +1,5 @@ +Error: schema/parse error in /tmp/cma2-run-infAQi/t1_add_three.ail.json: json: duplicate field `args` at line 41 column 4 + +Caused by: + 0: json: duplicate field `args` at line 41 column 4 + 1: duplicate field `args` at line 41 column 4 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_program.ail.json new file mode 100644 index 0000000..fe341de --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_program.ail.json @@ -0,0 +1,87 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ], + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "1" }, + { "t": "var", "name": "2" }, + { "t": "var", "name": "3" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "10" }, + { "t": "var", "name": "20" }, + { "t": "var", "name": "30" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_request.json new file mode 100644 index 0000000..6bf4a26 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-IBaQEp/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_response.json new file mode 100644 index 0000000..eddb06c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 663, + "prompt_tokens": 10091, + "total_tokens": 10754 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_check_stderr.txt new file mode 100644 index 0000000..188e180 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-Eu7zvu/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_program.ail.json new file mode 100644 index 0000000..cbdf4d4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_program.ail.json @@ -0,0 +1,82 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ], + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "1" }, + { "t": "var", "name": "2" }, + { "t": "var", "name": "3" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "10" }, + { "t": "var", "name": "20" }, + { "t": "var", "name": "30" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_request.json new file mode 100644 index 0000000..1aef5f8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-IBaQEp/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: Error: schema/parse error in /tmp/cma2-run-infAQi/t1_add_three.ail.json: json: duplicate field `args` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_response.json new file mode 100644 index 0000000..88abefd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 624, + "prompt_tokens": 10797, + "total_tokens": 11421 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_check_stderr.txt new file mode 100644 index 0000000..3b5a9be --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_check_stderr.txt @@ -0,0 +1,2 @@ +error: [arity-mismatch] add_three: arity mismatch for `+`: expected 2 args, got 1 +error: [unbound-var] main: unknown identifier: `1` diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_program.ail.json new file mode 100644 index 0000000..91533f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_program.ail.json @@ -0,0 +1,83 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" }, + { "t": "var", "name": "c" } + ] + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "1" }, + { "t": "var", "name": "2" }, + { "t": "var", "name": "3" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "10" }, + { "t": "var", "name": "20" }, + { "t": "var", "name": "30" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_request.json new file mode 100644 index 0000000..6d3783d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-IBaQEp/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: Error: schema/parse error in /tmp/cma2-run-infAQi/t1_add_three.ail.json: json: duplicate field `args` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-Eu7zvu/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_response.json new file mode 100644 index 0000000..d112ac9 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 626, + "prompt_tokens": 11451, + "total_tokens": 12077 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_check_stderr.txt new file mode 100644 index 0000000..3b041eb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_check_stderr.txt @@ -0,0 +1,5 @@ +Error: schema/parse error in /tmp/cma2-run-JDMIdn/t1_add_three.ail.json: json: duplicate field `args` at line 39 column 4 + +Caused by: + 0: json: duplicate field `args` at line 39 column 4 + 1: duplicate field `args` at line 39 column 4 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_program.ail.json new file mode 100644 index 0000000..fe79990 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_program.ail.json @@ -0,0 +1,85 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ], + "args": [ + { "t": "var", "name": "c" } + ] + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "1" }, + { "t": "var", "name": "2" }, + { "t": "var", "name": "3" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "var", "name": "10" }, + { "t": "var", "name": "20" }, + { "t": "var", "name": "30" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_request.json new file mode 100644 index 0000000..239edf2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-IBaQEp/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: Error: schema/parse error in /tmp/cma2-run-infAQi/t1_add_three.ail.json: json: duplicate field `args` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-Eu7zvu/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: error: [arity-mismatch] add_three: arity mismatch for `+`: expected 2 args, got 1\nerror: [unbound-var] main: unknown identifier: `1`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_response.json new file mode 100644 index 0000000..e8531db --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ],\n \"args\": [\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"1\" },\n { \"t\": \"var\", \"name\": \"2\" },\n { \"t\": \"var\", \"name\": \"3\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"10\" },\n { \"t\": \"var\", \"name\": \"20\" },\n { \"t\": \"var\", \"name\": \"30\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 633, + "prompt_tokens": 12128, + "total_tokens": 12761 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t1_add_three/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_check_stderr.txt new file mode 100644 index 0000000..3c50b88 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-o5IFGM/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_program.ail.json new file mode 100644 index 0000000..4f49f66 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_program.ail.json @@ -0,0 +1,134 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "var", "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_request.json new file mode 100644 index 0000000..9168099 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_response.json new file mode 100644 index 0000000..e0547c7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 983, + "prompt_tokens": 9459, + "total_tokens": 10442 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_check_stderr.txt new file mode 100644 index 0000000..580ff86 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-KoTSIo/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_program.ail.json new file mode 100644 index 0000000..06d0745 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_program.ail.json @@ -0,0 +1,132 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "var", "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_request.json new file mode 100644 index 0000000..48bdbb4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-o5IFGM/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_response.json new file mode 100644 index 0000000..b2d3470 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 979, + "prompt_tokens": 10471, + "total_tokens": 11450 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_check_stderr.txt new file mode 100644 index 0000000..0b1e1a7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-01xglj/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_program.ail.json new file mode 100644 index 0000000..4f49f66 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_program.ail.json @@ -0,0 +1,134 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "var", "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_request.json new file mode 100644 index 0000000..e807bae --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-o5IFGM/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-KoTSIo/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_response.json new file mode 100644 index 0000000..929b8dd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 983, + "prompt_tokens": 11479, + "total_tokens": 12462 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_check_stderr.txt new file mode 100644 index 0000000..d12893a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-96OTUA/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_program.ail.json new file mode 100644 index 0000000..4f49f66 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_program.ail.json @@ -0,0 +1,134 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "var", "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_request.json new file mode 100644 index 0000000..551ff6a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-o5IFGM/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-KoTSIo/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-01xglj/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_response.json new file mode 100644 index 0000000..ccb631b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 983, + "prompt_tokens": 12493, + "total_tokens": 13476 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_check_stderr.txt new file mode 100644 index 0000000..ef1727c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-A3PQgV/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_program.ail.json new file mode 100644 index 0000000..4f49f66 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_program.ail.json @@ -0,0 +1,134 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "var", "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_request.json new file mode 100644 index 0000000..603613b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-o5IFGM/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-KoTSIo/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-01xglj/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-96OTUA/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_response.json new file mode 100644 index 0000000..d68ce16 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil)))) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"var\", \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 983, + "prompt_tokens": 13506, + "total_tokens": 14489 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t2_length/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..6043765 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt @@ -0,0 +1,5 @@ +Error: schema/parse error in /tmp/cma2-run-NfQoeu/t3_main_prints.ail.json: json: invalid type: string "1337", expected internally tagged enum Literal at line 31 column 3 + +Caused by: + 0: json: invalid type: string "1337", expected internally tagged enum Literal at line 31 column 3 + 1: invalid type: string "1337", expected internally tagged enum Literal at line 31 column 3 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_program.ail.json new file mode 100644 index 0000000..57d1341 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_program.ail.json @@ -0,0 +1,32 @@ +{ + "schema": "ailang/v0", + "name": "t3_main_prints", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "doc": "Print 42 then 1337, each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 42 } } ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": "1337" } ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..a892330 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..ac6dbb5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print 42 then 1337, each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": \"1337\" } ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 239, + "prompt_tokens": 9406, + "total_tokens": 9645 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_program.ail.json new file mode 100644 index 0000000..ad48adb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_program.ail.json @@ -0,0 +1,32 @@ +{ + "schema": "ailang/v0", + "name": "t3_main_prints", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "doc": "Print 42 then 1337, each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 42 } } ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 1337 } } ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_request.json new file mode 100644 index 0000000..48109b7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print 42 then 1337, each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": \"1337\" } ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: Error: schema/parse error in /tmp/cma2-run-NfQoeu/t3_main_prints.ail.json: json: invalid type: string \"1337\", expected internally tagged enum Literal at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_response.json new file mode 100644 index 0000000..a058ae6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print 42 then 1337, each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 249, + "prompt_tokens": 9701, + "total_tokens": 9950 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt new file mode 100644 index 0000000..f12daf8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt @@ -0,0 +1,2 @@ +42 +1337 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt new file mode 100644 index 0000000..4fdd731 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-MFQgaI/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_program.ail.json new file mode 100644 index 0000000..1f7c2d0 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_program.ail.json @@ -0,0 +1,176 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count zeros in an Int list using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "letrec", + "name": "go", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] } ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["ys"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "ys" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + } + } + ] + }, + "in": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "xs" } ] + } + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "var", + "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "var", + "name": "Nil" + } + ] + } + ] + } + ] + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_request.json new file mode 100644 index 0000000..b0efd69 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_response.json new file mode 100644 index 0000000..2131257 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1364, + "prompt_tokens": 9486, + "total_tokens": 10850 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt new file mode 100644 index 0000000..771736b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-VVjs6b/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_program.ail.json new file mode 100644 index 0000000..0e1b20b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_program.ail.json @@ -0,0 +1,176 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count zeros in an Int list using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "letrec", + "name": "go", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["ys"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "ys" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } + ] + } + } + } + ] + }, + "in": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "xs" } ] + } + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "var", + "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "var", + "name": "Nil" + } + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_request.json new file mode 100644 index 0000000..37a37bc --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-MFQgaI/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_response.json new file mode 100644 index 0000000..b6bd5ff --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1361, + "prompt_tokens": 10880, + "total_tokens": 12241 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt new file mode 100644 index 0000000..464d017 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-4pkRQQ/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_program.ail.json new file mode 100644 index 0000000..d55aa29 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_program.ail.json @@ -0,0 +1,175 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count zeros in an Int list using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "letrec", + "name": "go", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["ys"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "ys" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } + ] + } + } + } + ] + }, + "in": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "xs" } ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "var", + "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "var", + "name": "Nil" + } + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_request.json new file mode 100644 index 0000000..25a7cfe --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-MFQgaI/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-VVjs6b/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_response.json new file mode 100644 index 0000000..ae420ae --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1362, + "prompt_tokens": 12271, + "total_tokens": 13633 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt new file mode 100644 index 0000000..da4ead6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-XNhPFz/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_program.ail.json new file mode 100644 index 0000000..336162f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_program.ail.json @@ -0,0 +1,175 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "con", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count zeros in an Int list using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "letrec", + "name": "go", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["ys"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "ys" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } + ] + } + } + } + ] + }, + "in": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "xs" } ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "var", + "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "var", + "name": "Nil" + } + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_request.json new file mode 100644 index 0000000..92364da --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-MFQgaI/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-VVjs6b/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-4pkRQQ/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_response.json new file mode 100644 index 0000000..00651bd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1359, + "prompt_tokens": 13663, + "total_tokens": 15022 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt new file mode 100644 index 0000000..8081f7d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-LVqX9A/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_program.ail.json new file mode 100644 index 0000000..336162f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_program.ail.json @@ -0,0 +1,175 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "con", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count zeros in an Int list using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "letrec", + "name": "go", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["ys"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "ys" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "t" } + ] + } + } + } + ] + }, + "in": { + "t": "app", + "fn": { "t": "var", "name": "go" }, + "args": [ { "t": "var", "name": "xs" } ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "var", + "name": "Nil" + } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "var", + "name": "Nil" + } + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_request.json new file mode 100644 index 0000000..10d98ac --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-MFQgaI/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-VVjs6b/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-4pkRQQ/prog.ail.json" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-XNhPFz/prog.ail.json" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_response.json new file mode 100644 index 0000000..5c06121 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count zeros in an Int list using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil))))))\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"var\",\n \"name\": \"Nil\"\n }\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1359, + "prompt_tokens": 15051, + "total_tokens": 16410 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/per_cohort/json/t4_count_zeros/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/scores.csv b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/scores.csv new file mode 100644 index 0000000..5c83a0e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/scores.csv @@ -0,0 +1,9 @@ +cohort,task_id,first_attempt_green,turns_to_green,prompt_tokens,completion_tokens,error_classes,final_status +json,t1_add_three,false,INF,53902,3172,check,turn_limit +json,t2_length,false,INF,57408,4911,check,turn_limit +json,t3_main_prints,false,2,19107,488,check,green +json,t4_count_zeros,false,INF,61351,6805,check,turn_limit +ailx,t1_add_three,false,3,19756,510,parse,green +ailx,t2_length,false,INF,34705,955,check;parse,turn_limit +ailx,t3_main_prints,true,1,6356,62,,green +ailx,t4_count_zeros,false,INF,34600,1060,parse,turn_limit diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/summary.md b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/summary.md new file mode 100644 index 0000000..59447e9 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/summary.md @@ -0,0 +1,22 @@ +# Cross-model authoring-form test — run summary + +## Cohort: json + +- tasks: 4 +- reached green: 1 +- first-attempt green: 0 +- mean turns-to-green (green-only): 2.00 +- total prompt tokens: 191768 +- total completion tokens: 15376 +- most common error class: check (x4) + +## Cohort: ailx + +- tasks: 4 +- reached green: 2 +- first-attempt green: 1 +- mean turns-to-green (green-only): 2.00 +- total prompt tokens: 95417 +- total completion tokens: 2587 +- most common error class: parse (x3) +