@shd101wyy/yo 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/skills/yo-async-effects/SKILL.md +15 -15
- package/.github/skills/yo-async-effects/async-effects-recipes.md +118 -121
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +33 -13
- package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +1 -1
- package/.github/skills/yo-syntax/SKILL.md +2 -2
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +108 -96
- package/README.md +6 -3
- package/out/cjs/index.cjs +812 -706
- package/out/cjs/yo-cli.cjs +1023 -907
- package/out/cjs/yo-lsp.cjs +836 -730
- package/out/esm/index.mjs +757 -651
- package/out/types/src/codegen/exprs/async.d.ts +2 -0
- package/out/types/src/codegen/exprs/await.d.ts +1 -0
- package/out/types/src/codegen/exprs/closures.d.ts +4 -0
- package/out/types/src/codegen/functions/context.d.ts +6 -0
- package/out/types/src/codegen/functions/declarations.d.ts +1 -1
- package/out/types/src/doc/model.d.ts +0 -1
- package/out/types/src/env.d.ts +2 -2
- package/out/types/src/evaluator/builtins/pragma.d.ts +9 -0
- package/out/types/src/evaluator/builtins/unsafe.d.ts +8 -0
- package/out/types/src/evaluator/context.d.ts +3 -1
- package/out/types/src/evaluator/exprs/{escape.d.ts → unwind.d.ts} +1 -1
- package/out/types/src/evaluator/index.d.ts +1 -1
- package/out/types/src/evaluator/memory-safety.d.ts +14 -0
- package/out/types/src/evaluator/types/flowability.d.ts +6 -0
- package/out/types/src/evaluator/types/function.d.ts +1 -2
- package/out/types/src/evaluator/utils.d.ts +0 -1
- package/out/types/src/expr-traversal.d.ts +1 -0
- package/out/types/src/expr.d.ts +9 -7
- package/out/types/src/public-safe-report.d.ts +19 -0
- package/out/types/src/tests/comptime-ref-gate.test.d.ts +1 -0
- package/out/types/src/tests/pragma-validation.test.d.ts +1 -0
- package/out/types/src/tests/public-safe-report.test.d.ts +1 -0
- package/out/types/src/tests/type-representation-pointer.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-gate.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-report-classify.test.d.ts +1 -0
- package/out/types/src/types/creators.d.ts +4 -6
- package/out/types/src/types/definitions.d.ts +9 -16
- package/out/types/src/types/guards.d.ts +1 -2
- package/out/types/src/types/tags.d.ts +0 -1
- package/out/types/src/types/utils.d.ts +5 -0
- package/out/types/src/unsafe-report.d.ts +29 -0
- package/out/types/src/value.d.ts +1 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/add-pragma-for-pointer-decls.ts +134 -0
- package/scripts/add-pragma.ts +58 -0
- package/scripts/migrate-amp-method-calls.ts +186 -0
- package/scripts/migrate-clone-calls.ts +93 -0
- package/scripts/migrate-get-unwrap.ts +166 -0
- package/scripts/migrate-index-patterns.ts +210 -0
- package/scripts/migrate-index-trait.ts +142 -0
- package/scripts/migrate-iterator.ts +150 -0
- package/scripts/migrate-self-ptr.ts +220 -0
- package/scripts/migrate-skip-pragmas.ts +109 -0
- package/scripts/migrate-tostring.ts +134 -0
- package/scripts/trim-pragma.ts +130 -0
- package/scripts/wrap-extern-calls.ts +161 -0
- package/std/alg/hash.yo +3 -2
- package/std/allocator.yo +6 -5
- package/std/async.yo +2 -2
- package/std/collections/array_list.yo +59 -40
- package/std/collections/btree_map.yo +19 -18
- package/std/collections/deque.yo +9 -8
- package/std/collections/hash_map.yo +101 -13
- package/std/collections/hash_set.yo +5 -4
- package/std/collections/linked_list.yo +39 -4
- package/std/collections/ordered_map.yo +3 -3
- package/std/collections/priority_queue.yo +14 -13
- package/std/crypto/md5.yo +2 -1
- package/std/crypto/random.yo +21 -20
- package/std/crypto/sha256.yo +2 -1
- package/std/encoding/base64.yo +18 -18
- package/std/encoding/hex.yo +5 -5
- package/std/encoding/json.yo +62 -13
- package/std/encoding/punycode.yo +24 -23
- package/std/encoding/toml.yo +4 -3
- package/std/encoding/utf16.yo +3 -3
- package/std/env.yo +43 -28
- package/std/error.yo +15 -3
- package/std/fmt/display.yo +2 -2
- package/std/fmt/index.yo +6 -5
- package/std/fmt/to_string.yo +39 -38
- package/std/fmt/writer.yo +9 -8
- package/std/fs/dir.yo +61 -66
- package/std/fs/file.yo +121 -126
- package/std/fs/metadata.yo +13 -18
- package/std/fs/temp.yo +35 -30
- package/std/fs/walker.yo +14 -19
- package/std/gc.yo +1 -0
- package/std/glob.yo +7 -7
- package/std/http/client.yo +33 -36
- package/std/http/http.yo +6 -6
- package/std/http/index.yo +4 -4
- package/std/imm/list.yo +33 -0
- package/std/imm/map.yo +2 -1
- package/std/imm/set.yo +1 -0
- package/std/imm/sorted_map.yo +1 -0
- package/std/imm/sorted_set.yo +1 -0
- package/std/imm/string.yo +27 -23
- package/std/imm/vec.yo +18 -2
- package/std/io/reader.yo +2 -1
- package/std/io/writer.yo +3 -2
- package/std/libc/assert.yo +1 -0
- package/std/libc/ctype.yo +1 -0
- package/std/libc/dirent.yo +1 -0
- package/std/libc/errno.yo +1 -0
- package/std/libc/fcntl.yo +1 -0
- package/std/libc/float.yo +1 -0
- package/std/libc/limits.yo +1 -0
- package/std/libc/math.yo +1 -0
- package/std/libc/signal.yo +1 -0
- package/std/libc/stdatomic.yo +1 -0
- package/std/libc/stdint.yo +1 -0
- package/std/libc/stdio.yo +1 -0
- package/std/libc/stdlib.yo +1 -0
- package/std/libc/string.yo +1 -0
- package/std/libc/sys/stat.yo +1 -0
- package/std/libc/time.yo +1 -0
- package/std/libc/unistd.yo +1 -0
- package/std/libc/wctype.yo +1 -0
- package/std/libc/windows.yo +2 -0
- package/std/log.yo +7 -6
- package/std/net/addr.yo +6 -5
- package/std/net/dns.yo +13 -16
- package/std/net/errors.yo +9 -9
- package/std/net/tcp.yo +71 -74
- package/std/net/udp.yo +40 -43
- package/std/os/signal.yo +5 -5
- package/std/path.yo +1 -0
- package/std/prelude.yo +377 -200
- package/std/process/command.yo +57 -46
- package/std/process/index.yo +2 -1
- package/std/regex/compiler.yo +10 -9
- package/std/regex/index.yo +41 -41
- package/std/regex/match.yo +2 -2
- package/std/regex/parser.yo +31 -31
- package/std/regex/vm.yo +42 -41
- package/std/string/string.yo +95 -40
- package/std/string/string_builder.yo +9 -9
- package/std/string/unicode.yo +50 -49
- package/std/sync/channel.yo +2 -1
- package/std/sync/cond.yo +5 -4
- package/std/sync/mutex.yo +4 -3
- package/std/sys/advise.yo +1 -0
- package/std/sys/bufio/buf_reader.yo +27 -26
- package/std/sys/bufio/buf_writer.yo +22 -21
- package/std/sys/clock.yo +1 -0
- package/std/sys/copy.yo +1 -0
- package/std/sys/dir.yo +10 -9
- package/std/sys/dns.yo +6 -5
- package/std/sys/errors.yo +12 -12
- package/std/sys/events.yo +1 -0
- package/std/sys/externs.yo +38 -37
- package/std/sys/file.yo +17 -16
- package/std/sys/future.yo +4 -3
- package/std/sys/iov.yo +1 -0
- package/std/sys/mmap.yo +1 -0
- package/std/sys/path.yo +1 -0
- package/std/sys/perm.yo +2 -1
- package/std/sys/pipe.yo +1 -0
- package/std/sys/process.yo +5 -4
- package/std/sys/signal.yo +1 -0
- package/std/sys/socketpair.yo +1 -0
- package/std/sys/sockinfo.yo +1 -0
- package/std/sys/statfs.yo +2 -1
- package/std/sys/statx.yo +1 -0
- package/std/sys/sysinfo.yo +1 -0
- package/std/sys/tcp.yo +15 -14
- package/std/sys/temp.yo +1 -0
- package/std/sys/time.yo +2 -1
- package/std/sys/timer.yo +6 -6
- package/std/sys/tty.yo +2 -1
- package/std/sys/udp.yo +13 -12
- package/std/sys/unix.yo +12 -11
- package/std/testing/bench.yo +4 -3
- package/std/thread.yo +7 -6
- package/std/time/datetime.yo +18 -15
- package/std/time/duration.yo +11 -10
- package/std/time/instant.yo +4 -4
- package/std/time/sleep.yo +1 -0
- package/std/url/index.yo +5 -5
- package/std/worker.yo +4 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yo-async-effects
|
|
3
|
-
description: Write Yo async code and algebraic effect handlers. Use this when working with
|
|
3
|
+
description: Write Yo async code and algebraic effect handlers. Use this when working with Io, Future, JoinHandle, ctl/fn handlers, io.async, io.await, io.spawn, return, and unwind.
|
|
4
4
|
argument-hint: "[async task, effect, or API]"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -14,33 +14,33 @@ If a repository wraps these primitives, keep the same semantics and verify wheth
|
|
|
14
14
|
|
|
15
15
|
Use this skill when you need to:
|
|
16
16
|
|
|
17
|
-
- write functions
|
|
17
|
+
- write functions that take an `io : Io` parameter
|
|
18
18
|
- return or consume `Future(...)` values
|
|
19
19
|
- run tasks with `io.async`, `io.await`, or `io.spawn`
|
|
20
|
-
- define
|
|
21
|
-
- reason about `return` versus `
|
|
20
|
+
- define handlers using `ctl(args) -> R` and install them as plain local bindings
|
|
21
|
+
- reason about `return` versus `unwind` in handlers
|
|
22
22
|
|
|
23
23
|
## Workflow
|
|
24
24
|
|
|
25
25
|
1. Decide whether the task needs sequential async, concurrent async on one thread, or true parallelism.
|
|
26
|
-
2. Add the
|
|
26
|
+
2. Add the effect parameters (`io : Io`, `raise : Raise`, …) to function signatures and call sites. There is no implicit injection — pass them explicitly.
|
|
27
27
|
3. Use the [async and effects recipes](./async-effects-recipes.md) for working patterns.
|
|
28
28
|
4. Re-check handler semantics before finalizing:
|
|
29
29
|
- `return(value)` resumes the continuation
|
|
30
|
-
- `
|
|
30
|
+
- `unwind(expr)` discards it (only valid inside a `ctl(...) -> R` body)
|
|
31
31
|
|
|
32
32
|
## High-signal rules
|
|
33
33
|
|
|
34
34
|
- `io.async(fn)` creates a lazy future; it does not start until awaited or spawned.
|
|
35
|
-
- `io.await(future)` runs or waits for the future and returns its result.
|
|
36
|
-
- `io.spawn(future)` starts it without waiting and returns `JoinHandle(T)`.
|
|
37
|
-
- `handle.await(
|
|
38
|
-
- Future types
|
|
39
|
-
- Effects are
|
|
40
|
-
- `
|
|
41
|
-
- `return(value)` inside a handler resumes the continuation; `
|
|
42
|
-
- `Exception` — non-resumable; handler calls `
|
|
43
|
-
-
|
|
35
|
+
- `io.await(future, e)` runs or waits for the future and returns its result. `e` is the effect bundle the future expects.
|
|
36
|
+
- `io.spawn(future, e)` starts it without waiting and returns `JoinHandle(T)`.
|
|
37
|
+
- `handle.await(io)` returns `Option(T)`; `.None` means the task aborted via `unwind`.
|
|
38
|
+
- Future types are `Future(T)` or `Future(T, E)` where `E` is a single effect bundle (typically a struct). Pack multiple effects into one struct rather than passing them as separate type arguments.
|
|
39
|
+
- Effects are passed as explicit parameters — pass them by name at call sites.
|
|
40
|
+
- A handler whose body may `unwind` must be typed `ctl(args) -> R`; otherwise type it `fn(args) -> R`. Subtyping is one-way: `fn(T) -> R <: ctl(T) -> R`.
|
|
41
|
+
- `return(value)` inside a handler resumes the continuation; `unwind(expr)` discards it and exits the install frame.
|
|
42
|
+
- `Exception` — non-resumable; handler calls `unwind(...)` to exit. `ResumableException(T)` — handler calls `return(...)` to resume.
|
|
43
|
+
- Closures cannot be `ctl` and cannot capture `ctl` values. Handlers are bare (non-capturing) anonymous functions.
|
|
44
44
|
- Yo async is single-threaded concurrency, not multithreaded parallelism.
|
|
45
45
|
|
|
46
46
|
## Resource
|
|
@@ -4,52 +4,42 @@ These patterns cover normal Yo async code and algebraic effects.
|
|
|
4
4
|
|
|
5
5
|
## Pick the right execution model
|
|
6
6
|
|
|
7
|
-
| Need | Pattern
|
|
8
|
-
| -------------------------- |
|
|
9
|
-
| Sequential async work | `result := io.await(task)`
|
|
10
|
-
| Start work and wait later | `handle := io.spawn(task)` then `handle.await(
|
|
11
|
-
| Yield to other ready tasks | `io.await(yield())`
|
|
12
|
-
| True multithreading | Use thread or parallelism APIs, not `io.async` alone
|
|
7
|
+
| Need | Pattern |
|
|
8
|
+
| -------------------------- | ------------------------------------------------------ |
|
|
9
|
+
| Sequential async work | `result := io.await(task, io)` |
|
|
10
|
+
| Start work and wait later | `handle := io.spawn(task, io)` then `handle.await(io)` |
|
|
11
|
+
| Yield to other ready tasks | `io.await(yield(), io)` |
|
|
12
|
+
| True multithreading | Use thread or parallelism APIs, not `io.async` alone |
|
|
13
13
|
|
|
14
14
|
## Minimal async function
|
|
15
15
|
|
|
16
16
|
```rust
|
|
17
17
|
{ yield } :: import("std/async");
|
|
18
18
|
|
|
19
|
-
pause_then_answer :: (fn(
|
|
20
|
-
io.async((
|
|
21
|
-
io.await(yield());
|
|
19
|
+
pause_then_answer :: (fn(io : Io) -> Impl(Future(i32, Io)))(
|
|
20
|
+
io.async((io : Io) => {
|
|
21
|
+
io.await(yield(), io);
|
|
22
22
|
i32(42)
|
|
23
23
|
})
|
|
24
24
|
);
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
- `io.async(...)` is lazy
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
```rust
|
|
32
|
-
// Equivalent — types inferred from Future(i32, IO, Raise) in the return type
|
|
33
|
-
work :: (fn(using(io : IO, raise : Raise)) -> Impl(Future(i32, IO, Raise)))(
|
|
34
|
-
io.async((using(my_io, my_raise)) => { // ← any names, no type annotations needed
|
|
35
|
-
my_io.await(yield());
|
|
36
|
-
my_raise("error")
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
```
|
|
27
|
+
- `io.async(...)` is lazy.
|
|
28
|
+
- The closure's parameter is the effect bundle. The simplest bundle is just `Io`.
|
|
29
|
+
- The `Future(T, E)` return type names the same bundle type that the closure consumes.
|
|
40
30
|
|
|
41
31
|
## Sequential await
|
|
42
32
|
|
|
43
33
|
```rust
|
|
44
34
|
{ yield } :: import("std/async");
|
|
45
35
|
|
|
46
|
-
main :: (fn(
|
|
47
|
-
task := io.async((
|
|
48
|
-
io.await(yield());
|
|
36
|
+
main :: (fn(io : Io) -> unit)({
|
|
37
|
+
task := io.async((io : Io) => {
|
|
38
|
+
io.await(yield(), io);
|
|
49
39
|
i32(1)
|
|
50
40
|
});
|
|
51
41
|
|
|
52
|
-
result := io.await(task);
|
|
42
|
+
result := io.await(task, io);
|
|
53
43
|
assert((result == i32(1)), "unexpected result");
|
|
54
44
|
});
|
|
55
45
|
|
|
@@ -61,38 +51,42 @@ export(main);
|
|
|
61
51
|
```rust
|
|
62
52
|
{ yield } :: import("std/async");
|
|
63
53
|
|
|
64
|
-
main :: (fn(
|
|
65
|
-
task1 := io.async((
|
|
66
|
-
io.await(yield());
|
|
54
|
+
main :: (fn(io : Io) -> unit)({
|
|
55
|
+
task1 := io.async((io : Io) => {
|
|
56
|
+
io.await(yield(), io);
|
|
67
57
|
i32(1)
|
|
68
58
|
});
|
|
69
|
-
task2 := io.async((
|
|
70
|
-
io.await(yield());
|
|
59
|
+
task2 := io.async((io : Io) => {
|
|
60
|
+
io.await(yield(), io);
|
|
71
61
|
i32(2)
|
|
72
62
|
});
|
|
73
63
|
|
|
74
|
-
handle1 := io.spawn(task1);
|
|
75
|
-
handle2 := io.spawn(task2);
|
|
64
|
+
handle1 := io.spawn(task1, io);
|
|
65
|
+
handle2 := io.spawn(task2, io);
|
|
76
66
|
|
|
77
|
-
result1 := handle1.await(
|
|
78
|
-
result2 := handle2.await(
|
|
67
|
+
result1 := handle1.await(io);
|
|
68
|
+
result2 := handle2.await(io);
|
|
79
69
|
});
|
|
80
70
|
|
|
81
71
|
export(main);
|
|
82
72
|
```
|
|
83
73
|
|
|
84
|
-
- `io.spawn(...)` begins execution without waiting
|
|
85
|
-
- `handle.await(
|
|
74
|
+
- `io.spawn(...)` begins execution without waiting.
|
|
75
|
+
- `handle.await(io)` returns `Option(T)` because a spawned task can abort via `unwind`.
|
|
86
76
|
|
|
87
77
|
## Propagating and handling effects
|
|
88
78
|
|
|
79
|
+
Handlers are typed `fn(...) -> R` when they only resume, and `ctl(...) -> R` when their
|
|
80
|
+
body may `unwind`. Use the local binding form `(name : EffectType) = ((args) -> { ... })`
|
|
81
|
+
to install a handler; lambdas on the RHS of `=` need outer parens.
|
|
82
|
+
|
|
89
83
|
```rust
|
|
90
84
|
open(import("std/fmt"));
|
|
91
85
|
open(import("std/string"));
|
|
92
86
|
|
|
93
|
-
Raise :: (
|
|
87
|
+
Raise :: (ctl(msg : String) -> i32);
|
|
94
88
|
|
|
95
|
-
safe_divide :: (fn(x : i32, y : i32,
|
|
89
|
+
safe_divide :: (fn(x : i32, y : i32, raise : Raise) -> i32)(
|
|
96
90
|
cond(
|
|
97
91
|
(y == i32(0)) => raise(`divide by zero`),
|
|
98
92
|
true => (x / y)
|
|
@@ -100,62 +94,75 @@ safe_divide :: (fn(x : i32, y : i32, using(raise : Raise)) -> i32)(
|
|
|
100
94
|
);
|
|
101
95
|
|
|
102
96
|
resume_example :: (fn() -> i32)({
|
|
103
|
-
|
|
97
|
+
// No `unwind` in this body — type the binding as the same Raise (a `ctl` is also a `fn`-compatible value when not unwinding).
|
|
98
|
+
// Use plain `fn(...) -> i32` if you want to forbid unwind altogether at this site.
|
|
99
|
+
(raise : Raise) = ((msg) -> {
|
|
104
100
|
println(msg);
|
|
105
101
|
return(i32(0));
|
|
106
102
|
});
|
|
107
103
|
|
|
108
|
-
safe_divide(i32(8), i32(0))
|
|
104
|
+
safe_divide(i32(8), i32(0), raise)
|
|
109
105
|
});
|
|
110
106
|
|
|
111
107
|
escape_example :: (fn() -> i32)({
|
|
112
|
-
(
|
|
108
|
+
(raise : Raise) = ((msg) -> {
|
|
113
109
|
println(msg);
|
|
114
|
-
|
|
110
|
+
unwind(i32(-1));
|
|
115
111
|
});
|
|
116
112
|
|
|
117
|
-
safe_divide(i32(8), i32(0))
|
|
113
|
+
safe_divide(i32(8), i32(0), raise)
|
|
118
114
|
});
|
|
119
115
|
```
|
|
120
116
|
|
|
121
|
-
| Handler action | Meaning
|
|
122
|
-
| --------------- |
|
|
123
|
-
| `return(value)` | Resume the continuation with `value`
|
|
124
|
-
| `
|
|
117
|
+
| Handler action | Meaning |
|
|
118
|
+
| --------------- | --------------------------------------------------------------------------------------- |
|
|
119
|
+
| `return(value)` | Resume the continuation with `value` |
|
|
120
|
+
| `unwind(expr)` | Exit the function that installed the handler. Only valid inside a `ctl(...) -> R` body. |
|
|
125
121
|
|
|
126
|
-
## Futures with multiple effects
|
|
122
|
+
## Futures with multiple effects — bundle them in a struct
|
|
123
|
+
|
|
124
|
+
`Future(T, E)` accepts a single effect type `E`. To carry several effects, declare a
|
|
125
|
+
bundle struct and pass that.
|
|
127
126
|
|
|
128
127
|
```rust
|
|
129
128
|
{ yield } :: import("std/async");
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
Raise :: (ctl(msg : String) -> i32);
|
|
131
|
+
TaskCtx :: struct(io : Io, raise : Raise);
|
|
132
|
+
|
|
133
|
+
work :: (fn(ctx : TaskCtx) -> Impl(Future(i32, TaskCtx)))(
|
|
134
|
+
io.async((ctx : TaskCtx) => {
|
|
135
|
+
ctx.io.await(yield(), ctx.io);
|
|
136
|
+
safe_divide(i32(10), i32(2), ctx.raise)
|
|
135
137
|
})
|
|
136
138
|
);
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
+
- The closure takes a single bundle parameter (e.g. `ctx : TaskCtx`).
|
|
142
|
+
- Inside the body, fields are accessed via dot (`ctx.io`, `ctx.raise`).
|
|
143
|
+
- The Future type names the same bundle struct: `Future(i32, TaskCtx)`.
|
|
144
|
+
- Build the bundle at the call site (`ctx := TaskCtx(io: io, raise: raise)`) and
|
|
145
|
+
pass it to `io.await` / `io.spawn`.
|
|
141
146
|
|
|
142
147
|
## Async recursion — use an iterative worklist instead
|
|
143
148
|
|
|
144
|
-
`recur` does **not** work inside an `io.async` lambda — it refers to the lambda's own signature, not the outer function
|
|
149
|
+
`recur` does **not** work inside an `io.async` lambda — it refers to the lambda's own signature, not the outer function. Calling the outer function by name is also forbidden in Yo. Attempting either will produce a compile-time error.
|
|
145
150
|
|
|
146
151
|
**Solution**: replace async recursion with an iterative worklist using `ArrayList` as a stack:
|
|
147
152
|
|
|
148
153
|
```rust
|
|
149
154
|
{ read_dir, DirEntry } :: import("std/fs/dir");
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
WalkCtx :: struct(io : Io, exn : Exception);
|
|
157
|
+
|
|
158
|
+
process_dir :: (fn(root: Path, ctx : WalkCtx) -> Impl(Future(unit, WalkCtx)))(
|
|
159
|
+
io.async((ctx : WalkCtx) => {
|
|
153
160
|
stack := ArrayList(Path).new();
|
|
154
161
|
{ stack.push(root); };
|
|
155
162
|
|
|
156
163
|
while(runtime((stack.len() > usize(0))), {
|
|
157
164
|
cur := match(stack.pop(), .Some(p) => p, .None => return());
|
|
158
|
-
entries := io.await(read_dir(cur));
|
|
165
|
+
entries := ctx.io.await(read_dir(cur, ctx.io), ctx.io);
|
|
159
166
|
// process `entries`, push subdirectories to `stack`
|
|
160
167
|
n := entries.len();
|
|
161
168
|
i := usize(0);
|
|
@@ -178,15 +185,26 @@ process_dir :: (fn(root: Path, using(io: IO, exn: Exception)) -> Impl(Future(uni
|
|
|
178
185
|
|
|
179
186
|
## Common pitfalls
|
|
180
187
|
|
|
181
|
-
- `io.async(...)` does not run immediately
|
|
182
|
-
- `
|
|
183
|
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
188
|
+
- `io.async(...)` does not run immediately.
|
|
189
|
+
- `unwind` is only valid inside a `ctl(...) -> R` body. From any other position
|
|
190
|
+
(match arm, `cond` branch, `begin` block, plain `fn` body), use `return`.
|
|
191
|
+
- `unwind` inside an async task aborts the future instead of completing it normally.
|
|
192
|
+
- `io.await(...)` on an already-aborted future can panic; `JoinHandle.await(...)` converts abort into `.None`.
|
|
193
|
+
- Closures cannot be `ctl`, and they cannot capture a `ctl`-typed value. Handlers are bare (non-capturing) anonymous functions. If you need to use a `ctl` handler from inside a closure body, pass it in as an explicit parameter instead of capturing it.
|
|
194
|
+
- Pointers and references to `ctl` types (or structs containing them) are rejected.
|
|
195
|
+
- **`recur` inside `io.async` calls the lambda, not the outer function** — use an iterative worklist for async recursion.
|
|
196
|
+
- **Closure params bound to a generic `E : Type.Struct` need an explicit annotation.** When a generic function takes `callback : Impl(Fn(v : V, e : E) -> R)` and you pass a closure, the closure's `e` parameter type cannot be inferred from the call's bundle argument — `E` is still unbound at the closure body's evaluation site. Define a concrete struct first and annotate the closure parameter:
|
|
197
|
+
```rust
|
|
198
|
+
// ✗ fails with "Cannot infer the type of anonymous closure parameter `e`"
|
|
199
|
+
traverse(arr, (v, e) => { e.log(v); ... }, { yield, log });
|
|
200
|
+
// ✓ annotate via a named bundle struct
|
|
201
|
+
Eff :: struct(yield : Yield, log : Log);
|
|
202
|
+
traverse(arr, (v : i32, e : Eff) => { e.log(v); ... }, Eff(yield, log));
|
|
203
|
+
```
|
|
186
204
|
|
|
187
205
|
## Exception (non-resumable)
|
|
188
206
|
|
|
189
|
-
`Exception` is a built-in struct-record effect for non-resumable error handling. When the handler calls `
|
|
207
|
+
`Exception` is a built-in struct-record effect for non-resumable error handling. When the handler calls `unwind`, the continuation is discarded:
|
|
190
208
|
|
|
191
209
|
```rust
|
|
192
210
|
open(import("std/error"));
|
|
@@ -196,7 +214,7 @@ DivError :: enum(DivByZero);
|
|
|
196
214
|
impl(DivError, ToString(to_string : ((self) -> `division by zero`)));
|
|
197
215
|
impl(DivError, Error());
|
|
198
216
|
|
|
199
|
-
safe_divide :: (fn(x : i32, y : i32,
|
|
217
|
+
safe_divide :: (fn(x : i32, y : i32, exn : Exception) -> i32)(
|
|
200
218
|
cond(
|
|
201
219
|
(y == i32(0)) => exn.throw(dyn(DivError.DivByZero)),
|
|
202
220
|
true => (x / y)
|
|
@@ -204,52 +222,51 @@ safe_divide :: (fn(x : i32, y : i32, using(exn : Exception)) -> i32)(
|
|
|
204
222
|
);
|
|
205
223
|
|
|
206
224
|
main :: (fn() -> unit)({
|
|
207
|
-
|
|
225
|
+
exn := Exception(
|
|
208
226
|
throw : ((err) -> {
|
|
209
227
|
println(`Error: ${err}`);
|
|
210
|
-
|
|
228
|
+
unwind(());
|
|
211
229
|
})
|
|
212
230
|
);
|
|
213
231
|
|
|
214
|
-
result := safe_divide(i32(10), i32(2));
|
|
232
|
+
result := safe_divide(i32(10), i32(2), exn);
|
|
215
233
|
println(`result: ${result}`);
|
|
216
234
|
|
|
217
|
-
safe_divide(i32(10), i32(0));
|
|
235
|
+
safe_divide(i32(10), i32(0), exn);
|
|
218
236
|
});
|
|
219
237
|
|
|
220
238
|
export(main);
|
|
221
239
|
```
|
|
222
240
|
|
|
223
|
-
- `Exception`
|
|
224
|
-
- `
|
|
225
|
-
-
|
|
226
|
-
-
|
|
241
|
+
- The struct constructor `Exception(...)` already pins the binding's type, so a plain `exn := Exception(...)` is enough — no `(exn : Exception) = ...` annotation needed. The annotation form is only required when the RHS is a raw lambda that has to commit to `ctl(...) -> R`.
|
|
242
|
+
- `Exception` has a single field `throw : (ctl(error : AnyError) -> T)`.
|
|
243
|
+
- `exn.throw(dyn(error))` calls the handler with a type-erased error.
|
|
244
|
+
- Handler uses `unwind` to discard the continuation and exit the enclosing function.
|
|
245
|
+
- Code after the escaped call is never reached.
|
|
227
246
|
|
|
228
247
|
### Swallowing exceptions with a fallback value (return in Exception handler)
|
|
229
248
|
|
|
230
|
-
When an exception is thrown inside an async operation (e.g., `cmd.status()` or `cmd.output()`), you can **swallow the error and resume with a fallback value** by using `return` in the handler (not `
|
|
249
|
+
When an exception is thrown inside an async operation (e.g., `cmd.status()` or `cmd.output()`), you can **swallow the error and resume with a fallback value** by using `return` in the handler (not `unwind`). The `ResumeType` is the return type of the operation that would have thrown.
|
|
231
250
|
|
|
232
251
|
```rust
|
|
233
252
|
{ Command, ExitStatus, Output } :: import("std/process/command");
|
|
234
253
|
|
|
235
254
|
// Check if a tool is available — returns false if it throws (e.g., not found)
|
|
236
|
-
|
|
255
|
+
try_exn := Exception(throw: ((err) -> {
|
|
237
256
|
return(ExitStatus(raw: i32(1))); // resume with "failed" exit status
|
|
238
257
|
}));
|
|
239
|
-
status := io.await(cmd.status(
|
|
258
|
+
status := io.await(cmd.status(io, try_exn), io);
|
|
240
259
|
available := status.success(); // false if exception was swallowed
|
|
241
260
|
|
|
242
261
|
// For cmd.output(), resume with a failed Output:
|
|
243
|
-
|
|
262
|
+
out_exn := Exception(throw: ((err) -> {
|
|
244
263
|
return(Output(status: ExitStatus(raw: i32(1)), stdout: ArrayList(u8).new(), stderr: ArrayList(u8).new()));
|
|
245
264
|
}));
|
|
246
|
-
out := io.await(cmd.output(
|
|
265
|
+
out := io.await(cmd.output(io, out_exn), io);
|
|
247
266
|
if((!(out.status.success())), { return(); }); // handle failure
|
|
248
267
|
```
|
|
249
268
|
|
|
250
|
-
Key: the `return` inside the handler resumes the _effect invocation site_ with the provided value. The calling code then sees the fallback as if the operation returned normally. Use `
|
|
251
|
-
|
|
252
|
-
**`escape(T_value)` constraint**: `escape(T_value)` inside an `Exception` handler requires that the enclosing `io.async` closure's return type matches `T_value`. Due to forward type inference, the evaluator may not know the closure's return type at the point where `given` is declared. This causes a "Expected: unit" error when `escape(non_unit)` is used in a handler declared before the final return expression. Prefer `return(fallback_value)` (resume) when possible.
|
|
269
|
+
Key: the `return` inside the handler resumes the _effect invocation site_ with the provided value. The calling code then sees the fallback as if the operation returned normally. Use `unwind` only when the enclosing function returns `unit` (e.g., test bodies).
|
|
253
270
|
|
|
254
271
|
`ResumableException(ResumeType)` is a struct-record effect for resumable error handling. The handler uses `return` to resume with a recovery value:
|
|
255
272
|
|
|
@@ -257,7 +274,7 @@ Key: the `return` inside the handler resumes the _effect invocation site_ with t
|
|
|
257
274
|
open(import("std/error"));
|
|
258
275
|
open(import("std/fmt"));
|
|
259
276
|
|
|
260
|
-
safe_divide :: (fn(x : i32, y : i32,
|
|
277
|
+
safe_divide :: (fn(x : i32, y : i32, exn : ResumableException(i32)) -> i32)(
|
|
261
278
|
cond(
|
|
262
279
|
(y == i32(0)) => exn.throw(dyn(`division by zero`)),
|
|
263
280
|
true => (x / y)
|
|
@@ -265,71 +282,51 @@ safe_divide :: (fn(x : i32, y : i32, using(exn : ResumableException(i32))) -> i3
|
|
|
265
282
|
);
|
|
266
283
|
|
|
267
284
|
main :: (fn() -> unit)({
|
|
268
|
-
|
|
285
|
+
exn := ResumableException(i32)(
|
|
269
286
|
throw : ((err) -> {
|
|
270
287
|
println(`Recovering from: ${err}`);
|
|
271
288
|
return(i32(0));
|
|
272
289
|
})
|
|
273
290
|
);
|
|
274
291
|
|
|
275
|
-
result := safe_divide(i32(10), i32(0));
|
|
292
|
+
result := safe_divide(i32(10), i32(0), exn);
|
|
276
293
|
assert((result == i32(0)), "recovered with 0");
|
|
277
294
|
});
|
|
278
295
|
|
|
279
296
|
export(main);
|
|
280
297
|
```
|
|
281
298
|
|
|
282
|
-
- Handler uses `return(value)` to resume the continuation with the recovery value
|
|
283
|
-
- The call site receives the returned value and continues normally
|
|
299
|
+
- Handler uses `return(value)` to resume the continuation with the recovery value.
|
|
300
|
+
- The call site receives the returned value and continues normally.
|
|
284
301
|
|
|
285
302
|
## Struct-record effects vs function-type effects
|
|
286
303
|
|
|
287
|
-
Effects in Yo can be plain function types or struct-record types
|
|
304
|
+
Effects in Yo can be plain function/ctl types or struct-record types that group several
|
|
305
|
+
operations:
|
|
288
306
|
|
|
289
307
|
```rust
|
|
290
|
-
Raise :: (
|
|
308
|
+
Raise :: (ctl(msg : String) -> i32);
|
|
291
309
|
|
|
292
310
|
Logger :: struct(
|
|
293
311
|
log : (fn(level : i32, msg : String) -> unit)
|
|
294
312
|
);
|
|
295
313
|
```
|
|
296
314
|
|
|
297
|
-
Both kinds
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
When an async future uses effects, include them in the `Future` type:
|
|
302
|
-
|
|
303
|
-
```rust
|
|
304
|
-
work :: (fn(using(io : IO, exn : Exception)) -> Impl(Future(i32, IO, Exception)))(
|
|
305
|
-
io.async((using(io : IO, exn : Exception)) => {
|
|
306
|
-
io.await(yield());
|
|
307
|
-
safe_divide(i32(10), i32(2), using(exn))
|
|
308
|
-
})
|
|
309
|
-
);
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
To spawn a task with effects, pass them explicitly via `using`:
|
|
313
|
-
|
|
314
|
-
```rust
|
|
315
|
-
handle := io.spawn(task, using(io, exn));
|
|
316
|
-
result := handle.await(using(io));
|
|
317
|
-
match(result,
|
|
318
|
-
.Some(value) => println(`got: ${value}`),
|
|
319
|
-
.None => println("task aborted via escape")
|
|
320
|
-
);
|
|
321
|
-
```
|
|
315
|
+
Both kinds are passed as explicit parameters. Struct-record effects group related
|
|
316
|
+
operations under a single nominal type — that pattern composes naturally with the
|
|
317
|
+
"single bundle struct" Future contract.
|
|
322
318
|
|
|
323
|
-
## Effect
|
|
319
|
+
## Effect-bundle polymorphism (advanced)
|
|
324
320
|
|
|
325
|
-
|
|
321
|
+
A function can be polymorphic over the effect bundle a Future carries by quantifying
|
|
322
|
+
over `E : Type.Struct`:
|
|
326
323
|
|
|
327
324
|
```rust
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
wait_then :: (fn(forall(T : Type, E : Type.Struct), fut : Impl(Future(T, E)), e : E) -> T)(
|
|
326
|
+
io.await(fut, e)
|
|
330
327
|
);
|
|
331
328
|
```
|
|
332
329
|
|
|
333
|
-
- `forall(
|
|
334
|
-
|
|
335
|
-
- See [ALGEBRAIC_EFFECTS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ALGEBRAIC_EFFECTS.md) for the full design
|
|
330
|
+
- `forall(E : Type.Struct)` constrains `E` to be a struct (so its fields can be looked
|
|
331
|
+
up at call sites and injected into the underlying state machine).
|
|
332
|
+
- See [ALGEBRAIC_EFFECTS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ALGEBRAIC_EFFECTS.md) for the full design.
|
|
@@ -105,6 +105,12 @@ match(numbers.get(usize(0)),
|
|
|
105
105
|
.None => ()
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
+
// Do NOT write the verbose forms when X(i) works:
|
|
109
|
+
// ✗ (&(numbers)).index(usize(0)).* = i32(99); // needs pragma, scans as raw-ptr code
|
|
110
|
+
// ✗ v := numbers.get(usize(0)).unwrap(); // panic-on-OOB anyway — just use call syntax
|
|
111
|
+
// ✓ numbers(usize(0)) = i32(99);
|
|
112
|
+
// ✓ v := numbers(usize(0));
|
|
113
|
+
|
|
108
114
|
counts := HashMap(String, i32).new();
|
|
109
115
|
counts.set(`yo`, i32(1));
|
|
110
116
|
```
|
|
@@ -123,7 +129,7 @@ counts.set(`yo`, i32(1));
|
|
|
123
129
|
```rust
|
|
124
130
|
Iterator :: trait(
|
|
125
131
|
Item : Type,
|
|
126
|
-
next : (fn(self :
|
|
132
|
+
next : (fn(ref(self) : Self) -> Option(Self.Item))
|
|
127
133
|
);
|
|
128
134
|
```
|
|
129
135
|
|
|
@@ -193,6 +199,15 @@ TcpStream :: object(fd : i32, buffer : ArrayList(u8));
|
|
|
193
199
|
|
|
194
200
|
- Use `newtype(...)` when the type has exactly one field
|
|
195
201
|
- Use `object(...)` for types that need shared ownership
|
|
202
|
+
- **Parameter form by type kind:**
|
|
203
|
+
- `object(...)`: plain `name : Type` (reference semantics — no pointer or ref needed).
|
|
204
|
+
`foo :: (fn(ctx : EvalContext) -> unit)(ctx.do_stuff());`
|
|
205
|
+
- `struct(...)` / `enum(...)` / primitive, read-only: plain `name : Type`.
|
|
206
|
+
- `struct(...)` / `enum(...)` / primitive, need mutation: `ref(name) : Type`.
|
|
207
|
+
`swap :: (fn(ref(a) : i32, ref(b) : i32) -> unit)(...);`
|
|
208
|
+
- Method receiver on `object`: plain `self : Self`.
|
|
209
|
+
- Method receiver on value type (traits + inherent mutators): `ref(self) : Self`.
|
|
210
|
+
- Raw FFI pointer: `name : *(T)` (requires `pragma(Pragma.AllowUnsafe);`).
|
|
196
211
|
- Source-file imports are namespace structs. The old `module(...)`, `Module`,
|
|
197
212
|
and `SelfModule` syntax is gone; use `struct(...)`, `Type`, and normal `Self`.
|
|
198
213
|
- Fields written as `name :: value` or `comptime(name) : Type` are compile-time-only static fields/methods and have no runtime layout
|
|
@@ -361,8 +376,8 @@ safe_div :: (fn(a : i32, b : i32) -> Result(i32, DivError))(
|
|
|
361
376
|
result := inc(i32(5));
|
|
362
377
|
|
|
363
378
|
transform :: (fn(values : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
|
|
364
|
-
for(values
|
|
365
|
-
|
|
379
|
+
for(values, ref(x) => {
|
|
380
|
+
x = f(x);
|
|
366
381
|
});
|
|
367
382
|
});
|
|
368
383
|
```
|
|
@@ -381,22 +396,27 @@ list := ArrayList(i32).new();
|
|
|
381
396
|
list.push(i32(1));
|
|
382
397
|
list.push(i32(2));
|
|
383
398
|
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
// Value form — implicit .into_iter().
|
|
400
|
+
for(list, (value) => {
|
|
401
|
+
println(value);
|
|
386
402
|
});
|
|
387
403
|
|
|
388
|
-
|
|
389
|
-
|
|
404
|
+
// Borrow form — implicit .iter() + .project(pos). `x` is a writable
|
|
405
|
+
// binding into the collection; assignments propagate back.
|
|
406
|
+
for(list, ref(x) => {
|
|
407
|
+
x = (x + i32(10));
|
|
390
408
|
});
|
|
391
409
|
```
|
|
392
410
|
|
|
393
|
-
|
|
|
394
|
-
|
|
|
395
|
-
|
|
|
396
|
-
|
|
|
411
|
+
| Form | Expansion | When to use |
|
|
412
|
+
| ----------------------------- | ------------------------------------------ | --------------------------------------------- |
|
|
413
|
+
| `for(coll, (x) => …)` | `coll.into_iter()`, yields `T` by value | Read-only iteration; combinator chains |
|
|
414
|
+
| `for(coll, ref(x) => …)` | `coll.iter()` + `coll.project(pos)` borrow | Mutation in place; writes propagate to `coll` |
|
|
415
|
+
| `for(chain.map(f), (x) => …)` | Treats chain as the iterator (value form) | Computed values; chains support only value |
|
|
397
416
|
|
|
398
|
-
-
|
|
399
|
-
-
|
|
417
|
+
- `Iterator` trait — defines `next() -> Option(Item)`. Custom iterables impl this.
|
|
418
|
+
- `IntoIterator` trait — defines `into_iter() -> IntoIter`. Collections impl this so `for(coll, ...)` works.
|
|
419
|
+
- `Indexable(Position)` trait — defines `project(pos) -> ref(Element)`. Collections impl this to support the borrow form.
|
|
400
420
|
|
|
401
421
|
## Module-level mutable variables
|
|
402
422
|
|
|
@@ -146,7 +146,7 @@ test("Async test", {
|
|
|
146
146
|
});
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
- `test("name", { body })` defines a test — `io :
|
|
149
|
+
- `test("name", { body })` defines a test — `io : Io` is automatically available
|
|
150
150
|
- All tests can use `io.async(...)`, `io.await(...)`, etc. without a `using` clause
|
|
151
151
|
- `assert(condition, "message")` — always include a message string
|
|
152
152
|
- `comptime_assert(expr)` — verified at compile time
|
|
@@ -32,11 +32,11 @@ Use this skill when you need to:
|
|
|
32
32
|
- `{ expr }` is a struct literal; `{ expr; }` is a begin block.
|
|
33
33
|
- Yo has no operator precedence. Parenthesize every binary operation.
|
|
34
34
|
- Use `func(arg)` with no space before `(` for every call; `func arg` and `func (arg)` are invalid.
|
|
35
|
-
- Use `return(value)` / `return()` and `
|
|
35
|
+
- Use `return(value)` / `return()` and `unwind(value)` / `unwind()`; bare control-flow arguments are invalid.
|
|
36
36
|
- Use `recur(...)` for self-recursion instead of the function name.
|
|
37
37
|
- Use `forall(T : Type)` for generic type parameters, `comptime(x) : T` for compile-time parameters.
|
|
38
38
|
- Use `where(T <: Trait)` to constrain type parameters.
|
|
39
|
-
-
|
|
39
|
+
- Effect parameters are explicit: name them in the function signature (e.g. `raise : Raise`, `exn : Exception`) and pass them at the call site. Install a handler locally with `name := Constructor(...)` for struct effects, or `(name : EffectType) = ((args) -> { ... })` when the RHS is a bare lambda that needs the `ctl(...) -> R` annotation.
|
|
40
40
|
- Use `(params) => expr` for closures; `Impl(Fn(...) -> T)` for the closure type.
|
|
41
41
|
- Every executable needs `export(main);`.
|
|
42
42
|
- Import sibling modules with relative paths like `./file.yo`.
|