Repository navigation
Expand file tree
/
Copy pathtodo_app.mere
More file actions
114 lines (101 loc) · 4.06 KB
/
Copy pathtodo_app.mere
File metadata and controls
114 lines (101 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// A small TODO list manager — a practical demo written in Mere.
//
// Purpose: combine OwnedVec / record / cap-passing (Logger) / vec_map /
// vec_iter / vec_fold / region so that in the first slice of Phase 16, we
// expose "what becomes frictional in real use".
//
// Structure:
// - Task is a record { id, text, done }
// - The list is OwnedVec[Task] (heap; we want to keep it across multiple
// functions)
// - Operations are logged via the Logger cap
//
// **Execution mode**: works on interpreter + C + LLVM + Wasm (Phase 16.3
// added `mk_logger` / `mk_metrics` 3-backend codegen support, Phase 16.4
// fixed a Wasm OwnedVec escape bug).
//
// Usage:
// dune exec ./bin/mere.exe -- examples/todo_app.mere
// # Expected output:
// # todo [INFO] added 4 tasks
// # todo [INFO] [ ] 1: buy milk ...
// # todo [INFO] undone count = 2
// # 4
type Task = { id: int, text: str, done: bool };
// Add a task with a fresh id. The next id is OwnedVec len + 1 — simple enough.
let add_task = fn tasks -> fn text ->
let next_id = owned_vec_len tasks + 1 in
let t = Task { id = next_id, text = text, done = false } in
let __ = owned_vec_push tasks t in
next_id
in
// Count incomplete tasks: we'd like to advance fold-style, but OwnedVec has
// no fold, so the route is to bring it into a region-local Vec via
// owned_vec_to_vec and use vec_fold.
// Friction #3: inside the vec_fold closure, type inference for `t.done`
// doesn't reach, so an explicit (t: Task) annotation is needed.
let count_undone = fn tasks ->
region R {
let v = owned_vec_to_vec tasks in
vec_fold v 0 (fn acc -> fn (t: Task) ->
if t.done then acc else acc + 1)
}
in
// Stream the list to the Logger (side effect via vec_iter).
// Friction #4: without spelling out lg: Logger, the lg.info field access
// inference inside the body fails (the parameter type stays a tyvar, since
// HM doesn't back-infer from field names).
let dump_tasks = fn (lg: Logger) -> fn tasks ->
region R {
let v = owned_vec_to_vec tasks in
vec_iter v (fn (t: Task) ->
let mark = if t.done then "[x] " else "[ ] " in
lg.info (mark ++ show t.id ++ ": " ++ t.text))
}
in
// How to mark a task "done" — design friction #1:
// OwnedVec[T] / Vec[R, T] is append-only (vec_set is Vec-only),
// and records are immutable. So "set id N's task done = true" means:
// a. Read all elements, build a new OwnedVec, and swap (current)
// b. (ideal) in-place update with owned_vec_set
// c. (ideal) direct mutation of struct fields
// Here we implement pattern (a).
let mark_done = fn tasks -> fn target_id ->
let n = owned_vec_len tasks in
region R {
// Move existing to a Vec, build an updated Vec, finally back to OwnedVec
let src = owned_vec_to_vec tasks in
let dst = vec_map src (fn (t: Task) ->
if t.id == target_id then
Task { id = t.id, text = t.text, done = true }
else t) in
// Allocate a new OwnedVec. The original tasks can't be "cleared", so in
// this design the caller must receive the new OwnedVec. Friction #2.
let new_tasks = vec_to_owned dst in
let __ = n in
new_tasks
}
in
// === main ===
// Build the Logger cap
let lg = mk_logger "todo" in
// Add a few tasks
let tasks = owned_vec_new () in
let __ = add_task tasks "buy milk" in
let __ = add_task tasks "write report" in
let __ = add_task tasks "fix codegen bug" in
let __ = add_task tasks "go run" in
let __ = lg.info ("added " ++ show (owned_vec_len tasks) ++ " tasks") in
let __ = dump_tasks lg tasks in
// Mark 2 and 4 done (mark_done returns a new OwnedVec — friction #2)
// Friction #5 (fixed in DEFERRED §1.4): the same-name rebinding
// `let tasks = mark_done tasks ...` was reworked into 2-step form in the
// second slice of Phase 16, so it now works in both interpreter and codegen
// — natural to write either way.
let tasks = mark_done tasks 2 in
let tasks = mark_done tasks 4 in
let __ = lg.info "after marking 2 and 4 done:" in
let __ = dump_tasks lg tasks in
let undone = count_undone tasks in
let __ = lg.info ("undone count = " ++ show undone) in
owned_vec_len tasks