-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path04_rows.affine
More file actions
70 lines (54 loc) · 2.81 KB
/
Copy path04_rows.affine
File metadata and controls
70 lines (54 loc) · 2.81 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
// SPDX-License-Identifier: MPL-2.0
// Warmup 04: Row polymorphism
//
// Row polymorphism lets you write functions that work on any record with at
// least the fields you need. It's the principled version of duck typing —
// with compile-time guarantees and no runtime surprises.
//
// Run: affinescript check 04_rows.affine
// ── Basic row-polymorphic function ────────────────────────────────────────────
//
// `[..r]` introduces a row variable. The function accepts any record that
// has an `x: Int` field, plus any other fields captured by `..r`.
fn get_x[..r](point: {x: Int, ..r}) -> Int {
point.x
}
// Both of these call sites work:
type Point2D = {x: Int, y: Int}
type Point3D = {x: Int, y: Int, z: Int}
let p2 = Point2D { x: 1, y: 2 }
let p3 = Point3D { x: 3, y: 4, z: 5 }
let x2 = get_x(p2) // fine — {x: Int, y: Int} has x
let x3 = get_x(p3) // fine — {x: Int, y: Int, z: Int} has x
// ── Extending a record ────────────────────────────────────────────────────────
//
// The spread syntax `..record` copies all fields of record into the new one.
fn with_id[..r](record: {..r}, id: Int) -> {id: Int, ..r} {
{ id: id, ..record }
}
let tagged_point = with_id(p2, 99) // { id: 99, x: 1, y: 2 }
// ── Named row type aliases ────────────────────────────────────────────────────
//
// You can name row constraints as type aliases:
type HasName[..r] = {name: String, ..r}
type HasAge[..r] = {age: Int, ..r}
fn greet_named[..r](entity: HasName[..r]) -> String {
"Hello, " ++ entity.name ++ "!"
}
// ── Row polymorphism in effects ───────────────────────────────────────────────
//
// Effect rows work the same way. `/ e` is a row variable over effects.
effect IO { fn println(s: String); }
effect Log { fn warn(s: String); }
fn run_with_log[..e](f: () -> () / IO + ..e) -> () / IO + Log + ..e {
Log.warn("starting");
f();
Log.warn("done");
}
// ── Exercise 1: write a function `swap_x_y` that takes any record with both ───
// `x: Int` and `y: Int` fields and returns one with the values swapped.
// Hint: the return type has the same row variable as the input.
// fn swap_x_y[..r](p: {x: Int, y: Int, ..r}) -> {x: Int, y: Int, ..r} { ... }
// ── Exercise 2: write a function `merge` that takes two single-field records ──
// and produces a record containing both fields.
// fn merge_name_age(named: {name: String}, aged: {age: Int}) -> {name: String, age: Int} { ... }