@shd101wyy/yo 0.1.16 → 0.1.18
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 +48 -0
- package/.github/skills/yo-async-effects/async-effects-recipes.md +262 -0
- package/.github/skills/yo-core-patterns/SKILL.md +48 -0
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +318 -0
- package/.github/skills/yo-project-workflow/SKILL.md +47 -0
- package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +255 -0
- package/.github/skills/yo-syntax/SKILL.md +57 -0
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +398 -0
- package/.github/skills/yo-wasm-integration/SKILL.md +46 -0
- package/.github/skills/yo-wasm-integration/wasm-integration-cheatsheet.md +250 -0
- package/README.md +9 -1
- package/out/cjs/index.cjs +241 -241
- package/out/cjs/yo-cli.cjs +616 -542
- package/out/cjs/yo-lsp.cjs +298 -298
- package/out/esm/index.mjs +2 -2
- package/out/types/src/doc/builder.d.ts +2 -0
- package/out/types/src/doc/model.d.ts +14 -1
- package/out/types/src/doc-command.d.ts +2 -0
- package/out/types/src/evaluator/builtins/build.d.ts +1 -0
- package/out/types/src/evaluator/index.d.ts +3 -0
- package/out/types/src/evaluator/values/impl.d.ts +14 -0
- package/out/types/src/skills-command.d.ts +4 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/scripts/build-site.ts +89 -26
- package/std/build.yo +7 -2
- package/std/collections/array_list.yo +3 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yo-async-effects
|
|
3
|
+
description: Write Yo async code and algebraic effect handlers. Use this when working with IO, Future, JoinHandle, using/given, io.async, io.await, io.spawn, return, and escape.
|
|
4
|
+
argument-hint: "[async task, effect, or API]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Yo Async and Effects
|
|
8
|
+
|
|
9
|
+
Use this skill for single-threaded async workflows and algebraic-effect-based APIs in Yo.
|
|
10
|
+
|
|
11
|
+
If a repository wraps these primitives, keep the same semantics and verify whether the wrapper changes naming only or behavior too.
|
|
12
|
+
|
|
13
|
+
## When to use this skill
|
|
14
|
+
|
|
15
|
+
Use this skill when you need to:
|
|
16
|
+
|
|
17
|
+
- write functions with `using(io : IO)`
|
|
18
|
+
- return or consume `Future(...)` values
|
|
19
|
+
- run tasks with `io.async`, `io.await`, or `io.spawn`
|
|
20
|
+
- define or handle effects via `using(...)` and `given(...)`
|
|
21
|
+
- reason about `return` versus `escape` in handlers
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
1. Decide whether the task needs sequential async, concurrent async on one thread, or true parallelism.
|
|
26
|
+
2. Add the necessary `using(...)` parameters to function signatures and call sites.
|
|
27
|
+
3. Use the [async and effects recipes](./async-effects-recipes.md) for working patterns.
|
|
28
|
+
4. Re-check handler semantics before finalizing:
|
|
29
|
+
- `return value` resumes the continuation
|
|
30
|
+
- `escape expr` discards it
|
|
31
|
+
|
|
32
|
+
## High-signal rules
|
|
33
|
+
|
|
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(using(io))` returns `Option(T)`; `.None` means the task aborted via `escape`.
|
|
38
|
+
- Future types include their effects: `Future(ResultType, IO, Effect...)`.
|
|
39
|
+
- Effects are matched by type, not variable name.
|
|
40
|
+
- `using(name : Type)` declares an implicit effect parameter; `given(name) := Type(...)` installs the handler.
|
|
41
|
+
- `return value` inside a handler resumes the continuation; `escape expr` discards it.
|
|
42
|
+
- `Exception` — non-resumable; handler calls `escape` to exit. `ResumableException(T)` — handler calls `return` to resume.
|
|
43
|
+
- Effect handlers are standalone, not closures; pass state explicitly.
|
|
44
|
+
- Yo async is single-threaded concurrency, not multithreaded parallelism.
|
|
45
|
+
|
|
46
|
+
## Resource
|
|
47
|
+
|
|
48
|
+
- [Yo async and effects recipes](./async-effects-recipes.md)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Yo Async and Effects Recipes
|
|
2
|
+
|
|
3
|
+
These patterns cover normal Yo async code and algebraic effects.
|
|
4
|
+
|
|
5
|
+
## Pick the right execution model
|
|
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(using(io))` |
|
|
11
|
+
| Yield to other ready tasks | `io.await(yield())` |
|
|
12
|
+
| True multithreading | Use thread or parallelism APIs, not `io.async` alone |
|
|
13
|
+
|
|
14
|
+
## Minimal async function
|
|
15
|
+
|
|
16
|
+
```rust
|
|
17
|
+
{ yield } :: import "std/async";
|
|
18
|
+
|
|
19
|
+
pause_then_answer :: (fn(using(io : IO)) -> Impl(Future(i32, IO)))(
|
|
20
|
+
io.async((using(io : IO)) => {
|
|
21
|
+
io.await(yield());
|
|
22
|
+
i32(42)
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `io.async(...)` is lazy
|
|
28
|
+
- If a function uses `using(io : IO)` and returns a future, include `IO` in the `Future(...)` type
|
|
29
|
+
|
|
30
|
+
## Sequential await
|
|
31
|
+
|
|
32
|
+
```rust
|
|
33
|
+
{ yield } :: import "std/async";
|
|
34
|
+
|
|
35
|
+
main :: (fn(using(io : IO)) -> unit)({
|
|
36
|
+
task := io.async((using(io : IO)) => {
|
|
37
|
+
io.await(yield());
|
|
38
|
+
i32(1)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
result := io.await(task);
|
|
42
|
+
assert((result == i32(1)), "unexpected result");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export main;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Concurrent tasks on the same thread
|
|
49
|
+
|
|
50
|
+
```rust
|
|
51
|
+
{ yield } :: import "std/async";
|
|
52
|
+
|
|
53
|
+
main :: (fn(using(io : IO)) -> unit)({
|
|
54
|
+
task1 := io.async((using(io : IO)) => {
|
|
55
|
+
io.await(yield());
|
|
56
|
+
i32(1)
|
|
57
|
+
});
|
|
58
|
+
task2 := io.async((using(io : IO)) => {
|
|
59
|
+
io.await(yield());
|
|
60
|
+
i32(2)
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
handle1 := io.spawn(task1);
|
|
64
|
+
handle2 := io.spawn(task2);
|
|
65
|
+
|
|
66
|
+
result1 := handle1.await(using(io));
|
|
67
|
+
result2 := handle2.await(using(io));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export main;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- `io.spawn(...)` begins execution without waiting
|
|
74
|
+
- `handle.await(using(io))` returns `Option(T)` because a spawned task can abort via `escape`
|
|
75
|
+
|
|
76
|
+
## Propagating and handling effects
|
|
77
|
+
|
|
78
|
+
```rust
|
|
79
|
+
open import "std/fmt";
|
|
80
|
+
open import "std/string";
|
|
81
|
+
|
|
82
|
+
Raise :: (fn(msg : String) -> i32);
|
|
83
|
+
|
|
84
|
+
safe_divide :: (fn(x : i32, y : i32, using(raise : Raise)) -> i32)(
|
|
85
|
+
cond(
|
|
86
|
+
(y == i32(0)) => raise(`divide by zero`),
|
|
87
|
+
true => (x / y)
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
resume_example :: (fn() -> i32)({
|
|
92
|
+
(given(raise) : Raise) = (fn(msg : String) -> i32)({
|
|
93
|
+
println(msg);
|
|
94
|
+
return i32(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
safe_divide(i32(8), i32(0))
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
escape_example :: (fn() -> i32)({
|
|
101
|
+
(given(raise) : Raise) = (fn(msg : String) -> i32)({
|
|
102
|
+
println(msg);
|
|
103
|
+
escape i32(-1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
safe_divide(i32(8), i32(0))
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Handler action | Meaning |
|
|
111
|
+
| -------------- | -------------------------------------------- |
|
|
112
|
+
| `return value` | Resume the continuation with `value` |
|
|
113
|
+
| `escape expr` | Exit the function that installed the handler |
|
|
114
|
+
|
|
115
|
+
## Futures with multiple effects
|
|
116
|
+
|
|
117
|
+
```rust
|
|
118
|
+
{ yield } :: import "std/async";
|
|
119
|
+
|
|
120
|
+
work :: (fn(using(io : IO, raise : Raise)) -> Impl(Future(i32, IO, Raise)))(
|
|
121
|
+
io.async((using(io : IO, raise : Raise)) => {
|
|
122
|
+
io.await(yield());
|
|
123
|
+
safe_divide(i32(10), i32(2), using(raise))
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- Every effect used by the future should appear in the `Future(...)` type
|
|
129
|
+
- Effects propagate through `using(...)` just like other contextual parameters
|
|
130
|
+
|
|
131
|
+
## Common pitfalls
|
|
132
|
+
|
|
133
|
+
- `io.async(...)` does not run immediately
|
|
134
|
+
- `escape` inside async aborts the future instead of completing it normally
|
|
135
|
+
- `io.await(...)` on an aborted future can panic; `JoinHandle.await(...)` converts abort into `.None`
|
|
136
|
+
- Handler functions cannot capture outer variables like closures; pass required state explicitly
|
|
137
|
+
|
|
138
|
+
## Exception (non-resumable)
|
|
139
|
+
|
|
140
|
+
`Exception` is a built-in module effect for non-resumable error handling. When the handler calls `escape`, the continuation is discarded:
|
|
141
|
+
|
|
142
|
+
```rust
|
|
143
|
+
open import "std/error";
|
|
144
|
+
open import "std/fmt";
|
|
145
|
+
|
|
146
|
+
DivError :: enum(DivByZero);
|
|
147
|
+
impl(DivError, ToString(to_string : ((self) -> `division by zero`)));
|
|
148
|
+
impl(DivError, Error());
|
|
149
|
+
|
|
150
|
+
safe_divide :: (fn(x : i32, y : i32, using(exn : Exception)) -> i32)(
|
|
151
|
+
cond(
|
|
152
|
+
(y == i32(0)) => exn.throw(dyn(DivError.DivByZero)),
|
|
153
|
+
true => (x / y)
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
main :: (fn() -> unit)({
|
|
158
|
+
given(exn) := Exception(
|
|
159
|
+
throw : ((err) -> {
|
|
160
|
+
println(`Error: ${err}`);
|
|
161
|
+
escape ();
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
result := safe_divide(i32(10), i32(2));
|
|
166
|
+
println(`result: ${result}`);
|
|
167
|
+
|
|
168
|
+
safe_divide(i32(10), i32(0));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export main;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- `Exception` has a single field `throw : (fn(error : AnyError) -> T)`
|
|
175
|
+
- `exn.throw(dyn(error))` calls the handler with a type-erased error
|
|
176
|
+
- Handler uses `escape` to discard the continuation and exit the enclosing function
|
|
177
|
+
- Code after the escaped call is never reached
|
|
178
|
+
|
|
179
|
+
## ResumableException
|
|
180
|
+
|
|
181
|
+
`ResumableException(ResumeType)` is a module effect for resumable error handling. The handler uses `return` to resume with a recovery value:
|
|
182
|
+
|
|
183
|
+
```rust
|
|
184
|
+
open import "std/error";
|
|
185
|
+
open import "std/fmt";
|
|
186
|
+
|
|
187
|
+
safe_divide :: (fn(x : i32, y : i32, using(exn : ResumableException(i32))) -> i32)(
|
|
188
|
+
cond(
|
|
189
|
+
(y == i32(0)) => exn.throw(dyn(`division by zero`)),
|
|
190
|
+
true => (x / y)
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
main :: (fn() -> unit)({
|
|
195
|
+
given(exn) := ResumableException(i32)(
|
|
196
|
+
throw : ((err) -> {
|
|
197
|
+
println(`Recovering from: ${err}`);
|
|
198
|
+
return i32(0);
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
result := safe_divide(i32(10), i32(0));
|
|
203
|
+
assert((result == i32(0)), "recovered with 0");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export main;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- Handler uses `return value` to resume the continuation with the recovery value
|
|
210
|
+
- The call site receives the returned value and continues normally
|
|
211
|
+
|
|
212
|
+
## Module effects vs function-type effects
|
|
213
|
+
|
|
214
|
+
Effects in Yo can be plain function types or module types:
|
|
215
|
+
|
|
216
|
+
```rust
|
|
217
|
+
Raise :: (fn(msg : String) -> i32);
|
|
218
|
+
|
|
219
|
+
Logger :: module(
|
|
220
|
+
log : (fn(level : i32, msg : String) -> unit)
|
|
221
|
+
);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Both kinds use `using(...)` / `given(...)` with the same semantics — they compile to evidence passing (function pointers as implicit C parameters). Module effects group related operations under a single name.
|
|
225
|
+
|
|
226
|
+
## Async with effects
|
|
227
|
+
|
|
228
|
+
When an async future uses effects, include them in the `Future` type:
|
|
229
|
+
|
|
230
|
+
```rust
|
|
231
|
+
work :: (fn(using(io : IO, exn : Exception)) -> Impl(Future(i32, IO, Exception)))(
|
|
232
|
+
io.async((using(io : IO, exn : Exception)) => {
|
|
233
|
+
io.await(yield());
|
|
234
|
+
safe_divide(i32(10), i32(2), using(exn))
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
To spawn a task with effects, pass them explicitly via `using`:
|
|
240
|
+
|
|
241
|
+
```rust
|
|
242
|
+
handle := io.spawn(task, using(io, exn));
|
|
243
|
+
result := handle.await(using(io));
|
|
244
|
+
match(result,
|
|
245
|
+
.Some(value) => println(`got: ${value}`),
|
|
246
|
+
.None => println("task aborted via escape")
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Effect row variables (advanced)
|
|
251
|
+
|
|
252
|
+
Functions can be polymorphic over their effects using spread parameters:
|
|
253
|
+
|
|
254
|
+
```rust
|
|
255
|
+
wrapper :: (fn(forall(...(E)), x : i32, using(...(E))) -> i32)(
|
|
256
|
+
safe_divide(x, i32(2))
|
|
257
|
+
);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
- `forall(...(E))` introduces an effect row variable
|
|
261
|
+
- `using(...(E))` forwards whatever effects the caller provides
|
|
262
|
+
- See [ALGEBRAIC_EFFECTS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ALGEBRAIC_EFFECTS.md) for the full design
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yo-core-patterns
|
|
3
|
+
description: Write everyday Yo application and library code. Use this when choosing Yo types, imports, strings, Option/Result, collections, traits, boxes, pointers, and standard-library modules.
|
|
4
|
+
argument-hint: "[feature, data type, or module]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Yo Core Patterns
|
|
8
|
+
|
|
9
|
+
Use this skill for normal Yo program structure and standard-library usage rather than compiler internals.
|
|
10
|
+
|
|
11
|
+
If a repository defines local wrappers or conventions, follow them after these baseline patterns.
|
|
12
|
+
|
|
13
|
+
## When to use this skill
|
|
14
|
+
|
|
15
|
+
Use this skill when you need to:
|
|
16
|
+
|
|
17
|
+
- pick between built-in types and standard-library types
|
|
18
|
+
- choose import paths for common Yo modules
|
|
19
|
+
- model optional values or recoverable errors
|
|
20
|
+
- write collection-heavy, string-heavy, or trait-based code
|
|
21
|
+
- handle boxes, pointers, and platform-specific branches
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
1. Identify whether the task is about data modeling, strings, containers, errors, or imports.
|
|
26
|
+
2. Choose the smallest built-in or standard-library type that fits the job.
|
|
27
|
+
3. Use the [core patterns cheatsheet](./core-patterns-cheatsheet.md) for imports, strings, `Option`/`Result`, traits, and collections.
|
|
28
|
+
4. Prefer standard modules before inventing custom helpers.
|
|
29
|
+
|
|
30
|
+
## High-signal rules
|
|
31
|
+
|
|
32
|
+
- `"` creates `str` in runtime code; template strings create `String`. In `comptime` functions, `"hello"` is `comptime_string` (distinct from `str`).
|
|
33
|
+
- Prefer template strings for constant `String` values.
|
|
34
|
+
- Prefer `print`/`println` from `std/fmt` over `printf`.
|
|
35
|
+
- `Option(T)` and `Result(T, E)` are the default nullable/error carriers.
|
|
36
|
+
- Use `rune` for Unicode code points, not `Char`.
|
|
37
|
+
- Model nullable pointers with `Option(*(T))` or `?*(T)`.
|
|
38
|
+
- Use `struct` for value types, `newtype` for single-field wrappers, `object` for reference-counted types.
|
|
39
|
+
- Use `forall` + `where` for generic impls; use `_` placeholder for partial application of comptime functions.
|
|
40
|
+
- Use `derive(Type, Eq, Hash, Clone, Ord, ToString)` to auto-generate common trait impls.
|
|
41
|
+
- Custom error types implement `ToString` + `Error`; wrap with `dyn(...)` into `AnyError`.
|
|
42
|
+
- Use `(params) => expr` for closures; `Impl(Fn(...) -> T)` for the closure type.
|
|
43
|
+
- Use `for collection.iter(), (item) => { ... }` for iteration.
|
|
44
|
+
- Indexed modules import cleanly as `std/url`, `std/regex`, `std/http`, `std/log`, and `std/glob`; multi-module families use explicit submodules.
|
|
45
|
+
|
|
46
|
+
## Resource
|
|
47
|
+
|
|
48
|
+
- [Yo core patterns cheatsheet](./core-patterns-cheatsheet.md)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Yo Core Patterns Cheatsheet
|
|
2
|
+
|
|
3
|
+
These patterns are aimed at everyday Yo application and library code.
|
|
4
|
+
|
|
5
|
+
## Strings and output
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
open import "std/fmt";
|
|
9
|
+
open import "std/string";
|
|
10
|
+
|
|
11
|
+
(name : str) = "yo";
|
|
12
|
+
greeting := `Hello ${name}`;
|
|
13
|
+
|
|
14
|
+
println(greeting);
|
|
15
|
+
println("plain str is also fine");
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- `"hello"` is a `str` literal in normal runtime code
|
|
19
|
+
- `` `hello ${name}` `` creates a `String`
|
|
20
|
+
- Prefer template strings for constant `String` values instead of `String.from("...")`
|
|
21
|
+
- Prefer `print`/`println` when a type implements `ToString`
|
|
22
|
+
|
|
23
|
+
### String type disambiguation
|
|
24
|
+
|
|
25
|
+
| Type | When you see it | Key behavior |
|
|
26
|
+
| ----------------- | -------------------------------------------- | -------------------------------------- |
|
|
27
|
+
| `str` | `"hello"` in runtime contexts | Slice of bytes, no ownership |
|
|
28
|
+
| `String` | Template strings `` `hello` `` | Owned UTF-8, reference-counted |
|
|
29
|
+
| `comptime_string` | `"hello"` inside `comptime` functions/macros | Compile-time only, distinct from `str` |
|
|
30
|
+
|
|
31
|
+
Key rules:
|
|
32
|
+
|
|
33
|
+
- In **runtime** code, `"hello"` is always `str`. Mixing literal and variable branches in `cond`/`match` works fine.
|
|
34
|
+
- In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string`. It does NOT auto-convert to `str`. Use `str.from_raw_parts(*(u8)("..."), usize(N))` if a comptime function needs to return `str`.
|
|
35
|
+
- For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
|
|
36
|
+
|
|
37
|
+
## Import patterns
|
|
38
|
+
|
|
39
|
+
```rust
|
|
40
|
+
{ LocalType } :: import "./local_type.yo";
|
|
41
|
+
open import "std/string";
|
|
42
|
+
{ ArrayList } :: import "std/collections/array_list";
|
|
43
|
+
{ HashMap } :: import "std/collections/hash_map";
|
|
44
|
+
{ Url } :: import "std/url";
|
|
45
|
+
{ Regex } :: import "std/regex";
|
|
46
|
+
{ fetch, HttpRequest } :: import "std/http";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Need | Import pattern |
|
|
50
|
+
| ------------------------------ | ------------------------------------------------------------------------------------ |
|
|
51
|
+
| Local module in same directory | `./file.yo` |
|
|
52
|
+
| Module with a clean index | `std/url`, `std/regex`, `std/http`, `std/log`, `std/glob` |
|
|
53
|
+
| Collections | `std/collections/array_list`, `std/collections/hash_map`, `std/collections/hash_set` |
|
|
54
|
+
| File system | `std/fs/file`, `std/fs/dir`, `std/path` |
|
|
55
|
+
| Networking | `std/net/tcp`, `std/net/udp`, `std/net/dns` |
|
|
56
|
+
|
|
57
|
+
Do not import `std/prelude`; it is already available.
|
|
58
|
+
|
|
59
|
+
## Option and Result
|
|
60
|
+
|
|
61
|
+
```rust
|
|
62
|
+
open import "std/string";
|
|
63
|
+
|
|
64
|
+
(value : Option(i32)) = .Some(i32(21));
|
|
65
|
+
doubled := value.map((x) => (x * i32(2)));
|
|
66
|
+
fallback := value.unwrap_or_else(() => i32(0));
|
|
67
|
+
|
|
68
|
+
(parsed : Result(i32, String)) = .Ok(i32(42));
|
|
69
|
+
text := match(parsed,
|
|
70
|
+
.Ok(n) => `value=${n}`,
|
|
71
|
+
.Err(err) => err
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Use `Option(T)` when absence is expected and ordinary
|
|
76
|
+
- Use `Result(T, E)` when the caller should handle failure
|
|
77
|
+
- Prefer combinators for straight-line transforms: `map`, `and_then`, `map_err`, `or_else`
|
|
78
|
+
- Switch to `match(...)` when branches need different logic or side effects
|
|
79
|
+
|
|
80
|
+
## Collections
|
|
81
|
+
|
|
82
|
+
```rust
|
|
83
|
+
{ ArrayList } :: import "std/collections/array_list";
|
|
84
|
+
{ HashMap } :: import "std/collections/hash_map";
|
|
85
|
+
open import "std/string";
|
|
86
|
+
|
|
87
|
+
numbers := ArrayList(i32).new();
|
|
88
|
+
numbers.push(i32(1));
|
|
89
|
+
numbers.push(i32(2));
|
|
90
|
+
|
|
91
|
+
counts := HashMap(String, i32).new();
|
|
92
|
+
counts.set(`yo`, i32(1));
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Type | Use when |
|
|
96
|
+
| ---------------- | ---------------------------------------- |
|
|
97
|
+
| `ArrayList(T)` | Ordered growable sequence |
|
|
98
|
+
| `HashMap(K, V)` | Key/value lookup with `Eq` + `Hash` keys |
|
|
99
|
+
| `HashSet(T)` | Membership tests and deduplication |
|
|
100
|
+
| `BTreeMap(K, V)` | Ordered map with `Ord` keys |
|
|
101
|
+
| `Deque(T)` | Push/pop on both ends |
|
|
102
|
+
| `String` | Owned UTF-8 text |
|
|
103
|
+
|
|
104
|
+
## Traits and associated types
|
|
105
|
+
|
|
106
|
+
```rust
|
|
107
|
+
Iterator :: trait(
|
|
108
|
+
Item : Type,
|
|
109
|
+
next : (fn(self : *(Self)) -> Option(Self.Item))
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Traits use labeled fields directly inside `trait(...)`
|
|
114
|
+
- Associated types are fields like `Item : Type` or `Output : Type`
|
|
115
|
+
- Wrap `fn` types in parentheses inside traits and type annotations
|
|
116
|
+
|
|
117
|
+
## Boxes, pointers, and nullability
|
|
118
|
+
|
|
119
|
+
```rust
|
|
120
|
+
counter := box(i32(0));
|
|
121
|
+
counter.* = (counter.* + i32(1));
|
|
122
|
+
|
|
123
|
+
(ptr : Option(*(u8))) = .None;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- Use `Box(T)` or `box(value)` for owned heap allocation
|
|
127
|
+
- Use `*(T)` for raw pointers
|
|
128
|
+
- Model nullable pointers as `Option(*(T))` or `?*(T)`, not sentinel integers
|
|
129
|
+
|
|
130
|
+
## Unicode and platform checks
|
|
131
|
+
|
|
132
|
+
```rust
|
|
133
|
+
{ Platform, platform } :: import "std/process";
|
|
134
|
+
|
|
135
|
+
separator := cond(
|
|
136
|
+
(platform == Platform.Windows) => `\\`,
|
|
137
|
+
true => `/`
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- Use `rune` for Unicode code points
|
|
142
|
+
- Branch on `platform` and `Platform` for OS-specific behavior
|
|
143
|
+
|
|
144
|
+
## Type categories
|
|
145
|
+
|
|
146
|
+
```rust
|
|
147
|
+
Point :: struct(x : i32, y : i32);
|
|
148
|
+
|
|
149
|
+
FilePermission :: newtype(mode : u32);
|
|
150
|
+
|
|
151
|
+
TcpStream :: object(fd : i32, buffer : ArrayList(u8));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Keyword | Semantics |
|
|
155
|
+
| -------------- | --------------------------------------- |
|
|
156
|
+
| `struct(...)` | Value type, copied on assignment |
|
|
157
|
+
| `newtype(...)` | Single-field value wrapper |
|
|
158
|
+
| `object(...)` | Reference-counted, shared on assignment |
|
|
159
|
+
|
|
160
|
+
- Use `newtype(...)` when the type has exactly one field
|
|
161
|
+
- Use `object(...)` for types that need shared ownership
|
|
162
|
+
|
|
163
|
+
## Impl blocks and generics
|
|
164
|
+
|
|
165
|
+
```rust
|
|
166
|
+
impl(Point,
|
|
167
|
+
distance : (fn(self : Self, other : Point) -> f64)({
|
|
168
|
+
dx := f64((self.x - other.x));
|
|
169
|
+
dy := f64((self.y - other.y));
|
|
170
|
+
sqrt(((dx * dx) + (dy * dy)))
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
impl(forall(T), where(T <: ToString), Box(T),
|
|
175
|
+
show : (fn(self : Self) -> unit)(
|
|
176
|
+
println(self.*)
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- Use `Self` inside impl method signatures
|
|
182
|
+
- `forall(T)` + `where(T <: Trait)` for generic impls
|
|
183
|
+
- Trait impls: `impl(MyType, MyTrait(args), : trait_field_bindings...)`
|
|
184
|
+
|
|
185
|
+
## Partial application
|
|
186
|
+
|
|
187
|
+
```rust
|
|
188
|
+
IntResult :: Result(_, i32);
|
|
189
|
+
(r : IntResult(bool)) = .Ok(true);
|
|
190
|
+
|
|
191
|
+
add :: (fn(comptime(x) : i32, comptime(y) : i32) -> comptime(i32))((x + y));
|
|
192
|
+
add1 :: add(i32(1), _);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- Use `_` placeholder in comptime function calls to partially apply
|
|
196
|
+
- Works with type constructors: `Result(_, i32)` makes a one-argument type function
|
|
197
|
+
- Only valid for functions with `comptime` return types
|
|
198
|
+
|
|
199
|
+
## Dynamic dispatch
|
|
200
|
+
|
|
201
|
+
```rust
|
|
202
|
+
(value : Dyn(ToString)) = dyn(i32(42));
|
|
203
|
+
println(value);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `Dyn(Trait)` is a type-erased trait object
|
|
207
|
+
- `dyn(expr)` wraps a concrete value into the trait object
|
|
208
|
+
- `Impl(Trait)` is the static dispatch counterpart
|
|
209
|
+
|
|
210
|
+
## Derive traits
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
Point :: struct(x : i32, y : i32);
|
|
214
|
+
derive(Point, Eq, Hash, Clone, Ord, ToString);
|
|
215
|
+
|
|
216
|
+
p1 := Point(1, 2);
|
|
217
|
+
p2 := p1.clone();
|
|
218
|
+
assert((p1 == p2), "equal after clone");
|
|
219
|
+
println(p1.to_string());
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- Built-in derivable traits: `Eq`, `Hash`, `Clone`, `Ord`, `ToString`
|
|
223
|
+
- Works for both structs and enums
|
|
224
|
+
- Custom derives can be registered with `derive_rule`; see [DERIVE_TRAITS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DERIVE_TRAITS.md)
|
|
225
|
+
|
|
226
|
+
## Error handling
|
|
227
|
+
|
|
228
|
+
```rust
|
|
229
|
+
open import "std/error";
|
|
230
|
+
|
|
231
|
+
DivError :: enum(DivByZero);
|
|
232
|
+
impl(DivError, ToString(to_string : ((self) -> `division by zero`)));
|
|
233
|
+
impl(DivError, Error());
|
|
234
|
+
|
|
235
|
+
safe_div :: (fn(a : i32, b : i32) -> Result(i32, DivError))(
|
|
236
|
+
cond(
|
|
237
|
+
(b == i32(0)) => .Err(.DivByZero),
|
|
238
|
+
true => .Ok((a / b))
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- Custom error types implement both `ToString` and `Error` traits
|
|
244
|
+
- `AnyError` is `Dyn(Error)` — wraps any error: `(err : AnyError) = dyn(MyError.Foo)`
|
|
245
|
+
- Use `downcast(err, MyError)` to recover the concrete type from `AnyError`
|
|
246
|
+
- For exception-style control flow, see [yo-async-effects](../yo-async-effects/SKILL.md)
|
|
247
|
+
|
|
248
|
+
## Closures as values
|
|
249
|
+
|
|
250
|
+
```rust
|
|
251
|
+
(inc : Impl(Fn(x : i32) -> i32)) = ((x) => (x + i32(1)));
|
|
252
|
+
result := inc(i32(5));
|
|
253
|
+
|
|
254
|
+
transform :: (fn(values : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
|
|
255
|
+
for values.iter(), (ptr) => {
|
|
256
|
+
ptr.* = f(ptr.*);
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `(params) => expr` creates a closure
|
|
262
|
+
- `Impl(Fn(params) -> ReturnType)` is the closure type
|
|
263
|
+
- Closures capture: value types by copy, object types by reference
|
|
264
|
+
- Each closure has a unique type
|
|
265
|
+
|
|
266
|
+
## Iterator and for loop
|
|
267
|
+
|
|
268
|
+
```rust
|
|
269
|
+
{ ArrayList } :: import "std/collections/array_list";
|
|
270
|
+
|
|
271
|
+
list := ArrayList(i32).new();
|
|
272
|
+
list.push(i32(1));
|
|
273
|
+
list.push(i32(2));
|
|
274
|
+
|
|
275
|
+
for list.iter(), (ptr) => {
|
|
276
|
+
println(ptr.*);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for list.into_iter(), (value) => {
|
|
280
|
+
println(value);
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
| Method | Yields | Semantics |
|
|
285
|
+
| -------------- | ------ | ----------------------------------- |
|
|
286
|
+
| `.iter()` | `*(T)` | Borrow via pointer; yields pointers |
|
|
287
|
+
| `.into_iter()` | `T` | Takes ownership; yields values |
|
|
288
|
+
|
|
289
|
+
- Implement `Iterator` trait to make custom types iterable
|
|
290
|
+
- Implement `IntoIterator` trait for collection-style iteration
|
|
291
|
+
|
|
292
|
+
## Module-level mutable variables
|
|
293
|
+
|
|
294
|
+
```rust
|
|
295
|
+
counter := i32(0);
|
|
296
|
+
|
|
297
|
+
inc :: (fn() -> unit)({
|
|
298
|
+
counter = (counter + i32(1));
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- Top-level `:=` creates a C `static` file-scope variable
|
|
303
|
+
- Cannot be exported; only compile-time values can be exported
|
|
304
|
+
- Not allowed inside `impl` blocks; use `::` for constants there
|
|
305
|
+
|
|
306
|
+
## Anonymous modules
|
|
307
|
+
|
|
308
|
+
```rust
|
|
309
|
+
my_module :: impl {
|
|
310
|
+
helper :: (fn(x : i32) -> i32)((x + i32(1)));
|
|
311
|
+
export helper;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
result := my_module.helper(i32(5));
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- `impl { ... }` creates a module namespace
|
|
318
|
+
- Only `::` (compile-time) bindings are allowed inside
|