Iter 14f: Boehm conservative GC
Decision 9 ships. Through Iter 14e every ADT box, lambda env, and
closure pair was leaked. This iter substitutes GC_malloc for malloc
in all four IR allocation sites and links -lgc. No language change,
no AST change, no schema change.
Diff: 5 files modified, ~30 LOC net.
- codegen/lib.rs: 4 substitutions @malloc -> @GC_malloc.
- ail/main.rs: .arg("-lgc") in the clang invocation.
- 5 IR snapshot files: mechanical s/@malloc/@GC_malloc/, 9
occurrences. IR is bit-identical to pre-14f modulo this
substitution — exactly Decision 9's promise.
- e2e.rs: new test gc_handles_recursive_list_construction.
- examples/gc_stress.{ailx,ail.json}: new fixture, builds a 50-
element list via recursive Cons, sums it (1275).
Hash invariance verified: every existing fixture def hash
unchanged (codegen and link line are downstream of canonical
bytes; AST didn't move).
Tests 79 -> 80, all green. Existing 79 byte-identical stdout.
gc_stress -> 1275. list_map_poly -> 2/3/4 unchanged. sort
sorted-list unchanged. cargo doc 0 warnings.
GC notes (pertinent to future work):
- GC_INIT() not needed on Arch libgc 1.5.6 (auto-init via
__attribute__((constructor))).
- No conservative-scan over-retention observed.
- -lgc alone sufficient for link (pthread/dl transitive).
Pattern-shape note from gc_stress fixture writing: the post-14d
"if-then-else" replacement is `(match (app == n 0) (case
(pat-lit true) ...) (case (pat-wild) ...))`. Three lines for
what `if` used to do in one, but uniform with the language.
Worth flagging for the stdlib brief.
Language is feature-complete enough for stdlib. The three
blockers identified at the 14b boundary (redundancy 14d, tail
calls 14e, GC 14f) are all done. Plan 15a: first stdlib module
std_list.ailx with length/append/reverse/map/filter/fold_left/
fold_right/head/tail/is_empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1513,6 +1513,10 @@ fn build_to(path: &Path, out: Option<PathBuf>, opt: &str) -> Result<PathBuf> {
|
||||
.arg("-o")
|
||||
.arg(&out_bin)
|
||||
.arg(&ll_path)
|
||||
// Boehm conservative GC (Decision 9 / Iter 14f). The lowered IR
|
||||
// calls @GC_malloc; libgc supplies it. Pthread/dl are pulled in
|
||||
// transitively via libgc.so on Linux, so a single -lgc suffices.
|
||||
.arg("-lgc")
|
||||
.status()
|
||||
.context("running clang")?;
|
||||
if !status.success() {
|
||||
|
||||
@@ -107,6 +107,25 @@ fn list_map_poly_inc_then_prints() {
|
||||
assert_eq!(lines, vec!["2", "3", "4"]);
|
||||
}
|
||||
|
||||
/// Iter 14f: stress the Boehm conservative GC integration end-to-end.
|
||||
/// `build 50` performs 50 `Cons` allocations via `GC_malloc`; `sum_list`
|
||||
/// walks the resulting `List Int` and prints the sum (1275 = 50*51/2).
|
||||
/// If GC is wired wrongly — missing `-lgc`, missing
|
||||
/// `declare ptr @GC_malloc(i64)`, libgc misbehaving on this build host —
|
||||
/// the test fails at link or run time. The match-on-Bool against
|
||||
/// `(== n 0)` shape is used because `(pat-lit 0)` against an `Int`
|
||||
/// scrutinee with a wildcard fallback is not currently in the grammar.
|
||||
#[test]
|
||||
fn gc_handles_recursive_list_construction() {
|
||||
let stdout = build_and_run("gc_stress.ail.json");
|
||||
assert_eq!(
|
||||
stdout.trim(),
|
||||
"1275",
|
||||
"expected sum [50,49,..,1] = 1275; \
|
||||
GC_malloc / -lgc integration may be broken"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 14e: `tail: true` annotation on `print_list`'s recursive
|
||||
/// call must reach LLVM as a `musttail call`. Asserted by emitting IR
|
||||
/// for list_map_poly and grepping for the exact instruction. This is
|
||||
|
||||
@@ -6,7 +6,7 @@ target triple = "<NORMALIZED>"
|
||||
|
||||
declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @malloc(i64)
|
||||
declare ptr @GC_malloc(i64)
|
||||
|
||||
@ail_hello_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_hello_main_adapter, ptr null }
|
||||
define i8 @ail_hello_main() {
|
||||
|
||||
@@ -6,7 +6,7 @@ target triple = "<NORMALIZED>"
|
||||
|
||||
declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @malloc(i64)
|
||||
declare ptr @GC_malloc(i64)
|
||||
|
||||
@ail_list_sum_list_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_sum_list_adapter, ptr null }
|
||||
@ail_list_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_main_adapter, ptr null }
|
||||
@@ -42,21 +42,21 @@ entry:
|
||||
|
||||
define i8 @ail_list_main() {
|
||||
entry:
|
||||
%v1 = call ptr @malloc(i64 8)
|
||||
%v1 = call ptr @GC_malloc(i64 8)
|
||||
store i64 0, ptr %v1, align 8
|
||||
%v2 = call ptr @malloc(i64 24)
|
||||
%v2 = call ptr @GC_malloc(i64 24)
|
||||
store i64 1, ptr %v2, align 8
|
||||
%v3 = getelementptr inbounds i8, ptr %v2, i64 8
|
||||
store i64 12, ptr %v3, align 8
|
||||
%v4 = getelementptr inbounds i8, ptr %v2, i64 16
|
||||
store ptr %v1, ptr %v4, align 8
|
||||
%v5 = call ptr @malloc(i64 24)
|
||||
%v5 = call ptr @GC_malloc(i64 24)
|
||||
store i64 1, ptr %v5, align 8
|
||||
%v6 = getelementptr inbounds i8, ptr %v5, i64 8
|
||||
store i64 20, ptr %v6, align 8
|
||||
%v7 = getelementptr inbounds i8, ptr %v5, i64 16
|
||||
store ptr %v2, ptr %v7, align 8
|
||||
%v8 = call ptr @malloc(i64 24)
|
||||
%v8 = call ptr @GC_malloc(i64 24)
|
||||
store i64 1, ptr %v8, align 8
|
||||
%v9 = getelementptr inbounds i8, ptr %v8, i64 8
|
||||
store i64 10, ptr %v9, align 8
|
||||
|
||||
@@ -6,7 +6,7 @@ target triple = "<NORMALIZED>"
|
||||
|
||||
declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @malloc(i64)
|
||||
declare ptr @GC_malloc(i64)
|
||||
|
||||
@ail_max3_max_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max_adapter, ptr null }
|
||||
@ail_max3_max3_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max3_adapter, ptr null }
|
||||
|
||||
@@ -6,7 +6,7 @@ target triple = "<NORMALIZED>"
|
||||
|
||||
declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @malloc(i64)
|
||||
declare ptr @GC_malloc(i64)
|
||||
|
||||
@ail_sum_sum_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_sum_adapter, ptr null }
|
||||
@ail_sum_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_main_adapter, ptr null }
|
||||
|
||||
@@ -6,7 +6,7 @@ target triple = "<NORMALIZED>"
|
||||
|
||||
declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @malloc(i64)
|
||||
declare ptr @GC_malloc(i64)
|
||||
|
||||
@ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null }
|
||||
@ail_ws_main_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_main_main_adapter, ptr null }
|
||||
|
||||
@@ -291,7 +291,7 @@ pub fn lower_workspace(ws: &Workspace) -> Result<String> {
|
||||
|
||||
out.push_str("declare i32 @printf(ptr, ...)\n");
|
||||
out.push_str("declare i32 @puts(ptr)\n");
|
||||
out.push_str("declare ptr @malloc(i64)\n\n");
|
||||
out.push_str("declare ptr @GC_malloc(i64)\n\n");
|
||||
out.push_str(&header);
|
||||
out.push_str(&body);
|
||||
|
||||
@@ -522,7 +522,7 @@ impl<'a> Emitter<'a> {
|
||||
Def::Type(_) => {
|
||||
// No LLVM definition needed: the ADT exists only as a
|
||||
// logical type. Heap boxes are allocated ad hoc via
|
||||
// malloc.
|
||||
// GC_malloc (Boehm conservative collector, Iter 14f).
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -935,7 +935,7 @@ impl<'a> Emitter<'a> {
|
||||
let size_bytes = 8 + (compiled.len() * 8) as i64;
|
||||
let p = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {p} = call ptr @malloc(i64 {size_bytes})\n"
|
||||
" {p} = call ptr @GC_malloc(i64 {size_bytes})\n"
|
||||
));
|
||||
// Write tag.
|
||||
self.body.push_str(&format!(
|
||||
@@ -1787,7 +1787,7 @@ impl<'a> Emitter<'a> {
|
||||
let env_ssa = if env_size > 0 {
|
||||
let env = self.fresh_ssa();
|
||||
self.body
|
||||
.push_str(&format!(" {env} = call ptr @malloc(i64 {env_size})\n"));
|
||||
.push_str(&format!(" {env} = call ptr @GC_malloc(i64 {env_size})\n"));
|
||||
for (i, (_cname, outer_ssa, cty, _c_ail, _sig)) in cap_meta.iter().enumerate() {
|
||||
let offset = (i * 8) as i64;
|
||||
let slot = self.fresh_ssa();
|
||||
@@ -1805,7 +1805,7 @@ impl<'a> Emitter<'a> {
|
||||
|
||||
let clos = self.fresh_ssa();
|
||||
self.body
|
||||
.push_str(&format!(" {clos} = call ptr @malloc(i64 16)\n"));
|
||||
.push_str(&format!(" {clos} = call ptr @GC_malloc(i64 16)\n"));
|
||||
let cs_t = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {cs_t} = getelementptr inbounds {{ ptr, ptr }}, ptr {clos}, i64 0, i32 0\n"
|
||||
|
||||
@@ -526,6 +526,62 @@ constructor-blocked recursions in `map`, `sort`, `insert`
|
||||
remain unmarked — they cannot benefit from `musttail`
|
||||
without a source-level rewrite.
|
||||
|
||||
## Decision 9: memory management — Boehm conservative GC
|
||||
|
||||
Through Iter 14e, every ADT box, lambda env, and closure pair was
|
||||
allocated with bare `malloc` and never freed. That worked for the
|
||||
17 test fixtures (all small, all short-lived) but is incompatible
|
||||
with any real workload — a stdlib `fold` over a million-element
|
||||
list would leak a million boxes. The "Goal" section's "no GC for
|
||||
the MVP" framing predates the parameterised-ADT pipeline (Iter 13)
|
||||
and the explicit-recursion expectation (Iter 14e); both make a
|
||||
collector necessary.
|
||||
|
||||
**Choice: Boehm-Demers-Weiser conservative GC.** The simplest
|
||||
working option:
|
||||
|
||||
- Replace `malloc(...)` with `GC_malloc(...)` in every IR site
|
||||
(currently `lower_ctor`'s ADT box, `lower_lambda`'s env block
|
||||
and closure pair).
|
||||
- Replace the IR-level `declare ptr @malloc(i64)` with
|
||||
`declare ptr @GC_malloc(i64)`.
|
||||
- Add `-lgc` to the `clang` link command (in
|
||||
`crates/ail/src/main.rs`'s `Build` / `Run` paths).
|
||||
- No language-level change. No AST change. No schema change.
|
||||
|
||||
Rationale:
|
||||
|
||||
- **Mature.** Boehm has been the default conservative GC for
|
||||
decades. Linux distros ship it as `libgc` / `libgc-dev` /
|
||||
`gc` (Arch).
|
||||
- **No language work.** Conservative scan of the C stack handles
|
||||
AILang's stack frames without LLVM stack-map infrastructure
|
||||
(which is its own multi-iter design).
|
||||
- **Single-iter integration.** Lift-and-shift of the four
|
||||
allocation sites; all existing tests must still pass with
|
||||
identical output.
|
||||
|
||||
Trade-offs accepted:
|
||||
|
||||
- **Conservative over-retention.** A user-supplied `Int` field
|
||||
whose value happens to coincide with a heap address will pin
|
||||
that allocation. In practice, vanishingly rare for typical
|
||||
values; survivable.
|
||||
- **Pause time non-deterministic.** Boehm uses stop-the-world
|
||||
mark-sweep. For LLM-author-written stdlib code at MVP scale,
|
||||
pause times are not the bottleneck.
|
||||
- **Build-time dependency.** `libgc` must be installed on the
|
||||
build host. Users without it get a link-time error from
|
||||
clang, not a silent failure.
|
||||
|
||||
A future iter may layer a per-fn-arena optimisation on top: when
|
||||
a fn's return type contains no boxed ADT, ADT boxes allocated
|
||||
inside that fn cannot escape, so an arena freed at fn return is
|
||||
sound by construction (per the 14e GC notes). That requires
|
||||
escape analysis, the corresponding AST/IR plumbing, and is its
|
||||
own design pass. Boehm-everything is the floor; arena is an
|
||||
optimisation above it.
|
||||
|
||||
## Mangling scheme (Iter 5c)
|
||||
|
||||
All AILang functions are mangled to `@ail_<module>_<def>` — even in the
|
||||
|
||||
@@ -2002,6 +2002,102 @@ Anything else (records as a primitive, nested patterns, local
|
||||
recursive let, type classes) can layer on later without forcing
|
||||
stdlib rewrites.
|
||||
|
||||
## Iter 14f — Boehm conservative GC
|
||||
|
||||
Decision 9 ships. Through Iter 14e every ADT box, lambda env,
|
||||
and closure pair was leaked. This iter substitutes
|
||||
`GC_malloc` for `malloc` in all four IR allocation sites and
|
||||
links `-lgc`. No language change, no AST change, no schema
|
||||
change.
|
||||
|
||||
**Diff: 5 files, ~30 LOC net.**
|
||||
|
||||
- `crates/ailang-codegen/src/lib.rs`: 4× `@malloc` → `@GC_malloc`
|
||||
(declare line + 3 call sites: `lower_ctor`, `lower_lambda`'s
|
||||
env, `lower_lambda`'s closure pair).
|
||||
- `crates/ail/src/main.rs`: `.arg("-lgc")` added to the clang
|
||||
invocation in `build_to`.
|
||||
- `crates/ail/tests/snapshots/{hello,sum,list,max3,ws_main}.ll`:
|
||||
mechanical s/@malloc/@GC_malloc/, 9 occurrences across 5
|
||||
files. The IR is bit-identical to pre-14f modulo this
|
||||
substitution — exactly Decision 9's promise.
|
||||
- `crates/ail/tests/e2e.rs`: new test
|
||||
`gc_handles_recursive_list_construction` (+19 LOC).
|
||||
- `examples/gc_stress.{ailx,ail.json}`: new fixture.
|
||||
|
||||
**Hash invariance verified.** Every existing fixture's def
|
||||
hashes are unchanged. The codegen and link line are downstream
|
||||
of canonical bytes; the AST schema didn't move; nothing on
|
||||
disk in `examples/*.ail.json` was touched. The new
|
||||
`gc_stress` module adds 4 new hashes, all unrelated.
|
||||
|
||||
**Tests: 80/80 (was 79).** Existing 79 produce byte-identical
|
||||
stdout — only the allocator changed, semantics unchanged. New:
|
||||
`gc_handles_recursive_list_construction` builds a List<Int> of
|
||||
length 50 via recursive `Cons`, sums it (`1275`). Manual smoke:
|
||||
|
||||
- `gc_stress.ail.json` → `1275`.
|
||||
- `list_map_poly` → `2 3 4` (unchanged).
|
||||
- `sort` → sorted list (unchanged).
|
||||
|
||||
`cargo doc --no-deps`: 0 warnings (DESIGN.md item 6 invariant
|
||||
preserved through nine iters of feature work).
|
||||
|
||||
**Pattern shape used in `gc_stress`** (caught a small typechecker
|
||||
constraint). The first proposed shape `(case (lit-int 0) Nil)`
|
||||
doesn't parse — `pat-lit` takes the bare literal token, not a
|
||||
keyword-prefixed form, and `case` requires a pattern. Worked
|
||||
shape: comparison-and-bool-match, mirroring `sort.ail.json`'s
|
||||
`<=` arm:
|
||||
|
||||
```
|
||||
(match (app == n 0)
|
||||
(case (pat-lit true) (term-ctor List Nil))
|
||||
(case (pat-wild) (term-ctor List Cons n (app build (app - n 1)))))
|
||||
```
|
||||
|
||||
This is the canonical "if-then-else" pattern post-14d. Worth
|
||||
flagging for the stdlib brief: predicates that need to branch
|
||||
go through the `==` / `<` / `<=` builtin returning Bool, then
|
||||
match on that Bool with a wildcard fallback. Three lines for
|
||||
what `if` used to do in one — but uniform with the rest of the
|
||||
language, no special case.
|
||||
|
||||
**GC integration notes.**
|
||||
- `GC_INIT()` is **not needed** on this build host (Arch
|
||||
with `libgc 1.5.6`). libgc auto-inits via
|
||||
`__attribute__((constructor))`.
|
||||
- No conservative-scan over-retention symptom observed: every
|
||||
existing test's stdout byte-identical; behaviour preserved.
|
||||
- `-lgc` alone is sufficient for the link; pthread/dl come in
|
||||
transitively from libgc.so's NEEDED entries.
|
||||
|
||||
**Language is feature-complete enough for stdlib.** Iters 14d
|
||||
(redundancy removal), 14e (explicit tail calls), 14f (GC) are
|
||||
the three blockers identified at the 14b boundary. They are
|
||||
all done. Anything else (records as primitive, nested patterns,
|
||||
local rec let, type classes) layers on later without forcing
|
||||
stdlib rewrite.
|
||||
|
||||
**Plan 15a.** First stdlib module: `examples/std/std_list.ailx`.
|
||||
Combinators: `length`, `append`, `reverse`, `map`, `filter`,
|
||||
`fold_left`, `fold_right`, `head`, `tail`, `is_empty`. Each
|
||||
combinator a fresh test vector for the parameterised-ADT +
|
||||
GC + tail-call combination. Authored in form (A) from day one;
|
||||
`.ail.json` produced via `ail parse`. Each combinator gets a
|
||||
dedicated e2e test.
|
||||
|
||||
If 15a surfaces compiler bugs (likely — every prior dogfood
|
||||
iter has, see 14a's monomorphisation bug), debugger handles
|
||||
them inline. If a compiler limitation surfaces that genuinely
|
||||
blocks the stdlib (e.g. nested patterns turn out to be needed),
|
||||
that becomes its own iter before 15a continues.
|
||||
|
||||
The architectural pin from Decision 6 governs: stdlib lives
|
||||
under `examples/std/` as `.ailx` source; tests load the
|
||||
generated `.ail.json`. `ailang-check` and `ailang-codegen`
|
||||
remain projection-agnostic.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"defs":[{"ctors":[{"fields":[],"name":"Nil"},{"fields":[{"k":"var","name":"a"},{"args":[{"k":"var","name":"a"}],"k":"con","name":"List"}],"name":"Cons"}],"doc":"Polymorphic singly-linked list (re-declared locally).","kind":"type","name":"List","vars":["a"]},{"body":{"arms":[{"body":{"args":[],"ctor":"Nil","t":"ctor","type":"List"},"pat":{"lit":{"kind":"bool","value":true},"p":"lit"}},{"body":{"args":[{"name":"n","t":"var"},{"args":[{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"}],"fn":{"name":"build","t":"var"},"t":"app"}],"ctor":"Cons","t":"ctor","type":"List"},"pat":{"p":"wild"}}],"scrutinee":{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"==","t":"var"},"t":"app"},"t":"match"},"doc":"Build [n, n-1, ..., 1] :: List Int via Cons recursion.","kind":"fn","name":"build","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"args":[{"k":"con","name":"Int"}],"k":"con","name":"List"}}},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"Nil","fields":[],"p":"ctor"}},{"body":{"args":[{"name":"h","t":"var"},{"args":[{"name":"t","t":"var"}],"fn":{"name":"sum_list","t":"var"},"t":"app"}],"fn":{"name":"+","t":"var"},"t":"app"},"pat":{"ctor":"Cons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Recursively sum a List Int.","kind":"fn","name":"sum_list","params":["xs"],"type":{"effects":[],"k":"fn","params":[{"args":[{"k":"con","name":"Int"}],"k":"con","name":"List"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":50},"t":"lit"}],"fn":{"name":"build","t":"var"},"t":"app"}],"fn":{"name":"sum_list","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"doc":"Build [50..1], sum, print 1275.","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"gc_stress","schema":"ailang/v0"}
|
||||
@@ -0,0 +1,50 @@
|
||||
; Iter 14f stress fixture for the Boehm conservative GC integration.
|
||||
; Builds a List Int of length 50 by recursive Cons construction,
|
||||
; sums it, prints the sum (1275 = 50*51/2).
|
||||
;
|
||||
; Match-on-Int isn't supported as a pattern shape; we drop into a
|
||||
; match-on-Bool against (== n 0), mirroring the sort fixture's
|
||||
; (<= y h) idiom. (pat-wild) catches the false case.
|
||||
|
||||
(module gc_stress
|
||||
|
||||
(data List (vars a)
|
||||
(doc "Polymorphic singly-linked list (re-declared locally).")
|
||||
(ctor Nil)
|
||||
(ctor Cons a (con List a)))
|
||||
|
||||
(fn build
|
||||
(doc "Build [n, n-1, ..., 1] :: List Int via Cons recursion.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int))
|
||||
(ret (con List (con Int)))))
|
||||
(params n)
|
||||
(body
|
||||
(match (app == n 0)
|
||||
(case (pat-lit true)
|
||||
(term-ctor List Nil))
|
||||
(case _
|
||||
(term-ctor List Cons
|
||||
n
|
||||
(app build (app - n 1)))))))
|
||||
|
||||
(fn sum_list
|
||||
(doc "Recursively sum a List Int.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con List (con Int)))
|
||||
(ret (con Int))))
|
||||
(params xs)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor Nil) 0)
|
||||
(case (pat-ctor Cons h t)
|
||||
(app + h (app sum_list t))))))
|
||||
|
||||
(fn main
|
||||
(doc "Build [50..1], sum, print 1275.")
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(do io/print_int (app sum_list (app build 50))))))
|
||||
Reference in New Issue
Block a user