@rcrsr/rill 0.2.4 → 0.4.2
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/README.md +58 -30
- package/dist/check/visitor.d.ts.map +1 -1
- package/dist/check/visitor.js +3 -0
- package/dist/check/visitor.js.map +1 -1
- package/dist/lexer/tokenizer.d.ts.map +1 -1
- package/dist/lexer/tokenizer.js +3 -0
- package/dist/lexer/tokenizer.js.map +1 -1
- package/dist/parser/helpers.d.ts.map +1 -1
- package/dist/parser/helpers.js +16 -0
- package/dist/parser/helpers.js.map +1 -1
- package/dist/parser/parser-expr.d.ts.map +1 -1
- package/dist/parser/parser-expr.js +48 -13
- package/dist/parser/parser-expr.js.map +1 -1
- package/dist/parser/parser-literals.d.ts +2 -1
- package/dist/parser/parser-literals.d.ts.map +1 -1
- package/dist/parser/parser-literals.js +63 -6
- package/dist/parser/parser-literals.js.map +1 -1
- package/dist/parser/parser-variables.js +5 -0
- package/dist/parser/parser-variables.js.map +1 -1
- package/dist/runtime/core/callable.d.ts +22 -4
- package/dist/runtime/core/callable.d.ts.map +1 -1
- package/dist/runtime/core/callable.js +45 -3
- package/dist/runtime/core/callable.js.map +1 -1
- package/dist/runtime/core/context.d.ts.map +1 -1
- package/dist/runtime/core/context.js +1 -0
- package/dist/runtime/core/context.js.map +1 -1
- package/dist/runtime/core/equals.d.ts.map +1 -1
- package/dist/runtime/core/equals.js +32 -4
- package/dist/runtime/core/equals.js.map +1 -1
- package/dist/runtime/core/eval/base.d.ts.map +1 -1
- package/dist/runtime/core/eval/base.js +4 -1
- package/dist/runtime/core/eval/base.js.map +1 -1
- package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -1
- package/dist/runtime/core/eval/mixins/closures.js +71 -0
- package/dist/runtime/core/eval/mixins/closures.js.map +1 -1
- package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -1
- package/dist/runtime/core/eval/mixins/core.js +293 -8
- package/dist/runtime/core/eval/mixins/core.js.map +1 -1
- package/dist/runtime/core/eval/mixins/literals.d.ts +2 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -1
- package/dist/runtime/core/eval/mixins/literals.js +446 -39
- package/dist/runtime/core/eval/mixins/literals.js.map +1 -1
- package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -1
- package/dist/runtime/core/eval/mixins/variables.js +42 -2
- package/dist/runtime/core/eval/mixins/variables.js.map +1 -1
- package/dist/types.d.ts +20 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/docs/00_INDEX.md +4 -4
- package/docs/02_types.md +77 -2
- package/docs/04_operators.md +57 -0
- package/docs/06_closures.md +438 -4
- package/docs/11_reference.md +214 -13
- package/docs/15_grammar.ebnf +55 -5
- package/docs/18_design-principles.md +37 -0
- package/docs/19_cookbook.md +628 -0
- package/docs/99_llm-reference.txt +268 -14
- package/package.json +1 -1
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
# rill Cookbook
|
|
2
|
+
|
|
3
|
+
*Advanced patterns for workflow orchestration*
|
|
4
|
+
|
|
5
|
+
This cookbook demonstrates idiomatic rill patterns for common programming tasks. Each recipe shows a complete, working example.
|
|
6
|
+
|
|
7
|
+
## State Machines
|
|
8
|
+
|
|
9
|
+
State machines model systems with discrete states and transitions. Rill's dispatch operator makes state machines declarative and readable.
|
|
10
|
+
|
|
11
|
+
### Basic State Machine
|
|
12
|
+
|
|
13
|
+
Use nested dict dispatch to look up transitions by state and event:
|
|
14
|
+
|
|
15
|
+
```rill
|
|
16
|
+
# Traffic light state machine
|
|
17
|
+
[
|
|
18
|
+
green: [tick: "yellow", emergency: "red"],
|
|
19
|
+
yellow: [tick: "red", emergency: "red"],
|
|
20
|
+
red: [tick: "green", emergency: "red"]
|
|
21
|
+
] :> $machine
|
|
22
|
+
|
|
23
|
+
# Current state and incoming event
|
|
24
|
+
"green" :> $state
|
|
25
|
+
"tick" :> $event
|
|
26
|
+
|
|
27
|
+
# Look up next state
|
|
28
|
+
$machine.$state.$event
|
|
29
|
+
# Result: "yellow"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The pattern `$machine.$state.$event` chains two property accesses: first get the state's transition table, then get the event's target state.
|
|
33
|
+
|
|
34
|
+
### State Machine with Actions
|
|
35
|
+
|
|
36
|
+
Embed closures in the transition table to execute side effects:
|
|
37
|
+
|
|
38
|
+
```rill
|
|
39
|
+
# Order processing state machine
|
|
40
|
+
[
|
|
41
|
+
pending: [
|
|
42
|
+
pay: [next: "paid", action: ||{ log("Payment received") }],
|
|
43
|
+
cancel: [next: "cancelled", action: ||{ log("Order cancelled") }]
|
|
44
|
+
],
|
|
45
|
+
paid: [
|
|
46
|
+
ship: [next: "shipped", action: ||{ log("Order shipped") }],
|
|
47
|
+
refund: [next: "refunded", action: ||{ log("Refund issued") }]
|
|
48
|
+
],
|
|
49
|
+
shipped: [
|
|
50
|
+
deliver: [next: "delivered", action: ||{ log("Order delivered") }]
|
|
51
|
+
]
|
|
52
|
+
] :> $machine
|
|
53
|
+
|
|
54
|
+
"pending" :> $state
|
|
55
|
+
"pay" :> $event
|
|
56
|
+
|
|
57
|
+
# Get transition
|
|
58
|
+
$machine.$state.$event :> $transition
|
|
59
|
+
|
|
60
|
+
# Execute action and get next state
|
|
61
|
+
$transition.action()
|
|
62
|
+
$transition.next
|
|
63
|
+
# Result: "paid"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### State Machine Loop
|
|
67
|
+
|
|
68
|
+
Process a sequence of events through the machine:
|
|
69
|
+
|
|
70
|
+
```rill
|
|
71
|
+
# Define machine
|
|
72
|
+
[
|
|
73
|
+
idle: [start: "running", stop: "idle"],
|
|
74
|
+
running: [pause: "paused", stop: "idle"],
|
|
75
|
+
paused: [resume: "running", stop: "idle"]
|
|
76
|
+
] :> $machine
|
|
77
|
+
|
|
78
|
+
# Event sequence to process
|
|
79
|
+
["start", "pause", "resume", "stop"] :> $events
|
|
80
|
+
|
|
81
|
+
# Process events, accumulating state history
|
|
82
|
+
$events -> fold("idle") {
|
|
83
|
+
$machine.($@).($) # $@ is accumulator (current state), $ is event
|
|
84
|
+
}
|
|
85
|
+
# Result: "idle"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Track state history with `fold`:
|
|
89
|
+
|
|
90
|
+
```rill
|
|
91
|
+
[
|
|
92
|
+
idle: [start: "running"],
|
|
93
|
+
running: [pause: "paused", stop: "idle"],
|
|
94
|
+
paused: [resume: "running"]
|
|
95
|
+
] :> $machine
|
|
96
|
+
|
|
97
|
+
["start", "pause", "resume"] :> $events
|
|
98
|
+
|
|
99
|
+
$events -> fold([current: "idle", history: []]) {
|
|
100
|
+
$ :> $event
|
|
101
|
+
$machine.($@.current) :> $stateConfig
|
|
102
|
+
$stateConfig -> .keys -> .has($event) ? {
|
|
103
|
+
$stateConfig.($event) :> $next
|
|
104
|
+
[current: $next, history: [...$@.history, $next]]
|
|
105
|
+
} ! $@
|
|
106
|
+
}
|
|
107
|
+
# Result: [current: "running", history: ["running", "paused", "running"]]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Guard Conditions
|
|
111
|
+
|
|
112
|
+
Use closures as guards to conditionally allow transitions:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
# Turnstile with coin counting (conceptual - dict spread not implemented)
|
|
116
|
+
[
|
|
117
|
+
locked: [
|
|
118
|
+
coin: [
|
|
119
|
+
guard: |ctx|($ctx.coins >= 1),
|
|
120
|
+
next: "unlocked",
|
|
121
|
+
update: |ctx|([...$ctx, coins: $ctx.coins - 1])
|
|
122
|
+
]
|
|
123
|
+
],
|
|
124
|
+
unlocked: [
|
|
125
|
+
push: [next: "locked"],
|
|
126
|
+
coin: [
|
|
127
|
+
next: "unlocked",
|
|
128
|
+
update: |ctx|([...$ctx, coins: $ctx.coins + 1])
|
|
129
|
+
]
|
|
130
|
+
]
|
|
131
|
+
] :> $machine
|
|
132
|
+
|
|
133
|
+
# Initial context
|
|
134
|
+
[state: "locked", coins: 2] :> $ctx
|
|
135
|
+
|
|
136
|
+
# Process coin event
|
|
137
|
+
$machine.($ctx.state).coin :> $transition
|
|
138
|
+
|
|
139
|
+
# Check guard if present
|
|
140
|
+
$transition.?guard ? {
|
|
141
|
+
$transition.guard($ctx) ? {
|
|
142
|
+
# Guard passed - apply update and transition
|
|
143
|
+
$transition.?update
|
|
144
|
+
? $transition.update($ctx)
|
|
145
|
+
! $ctx
|
|
146
|
+
-> [...$, state: $transition.next]
|
|
147
|
+
} ! $ctx # Guard failed, no transition
|
|
148
|
+
} ! {
|
|
149
|
+
# No guard - always transition
|
|
150
|
+
$transition.?update
|
|
151
|
+
? $transition.update($ctx)
|
|
152
|
+
! $ctx
|
|
153
|
+
-> [...$, state: $transition.next]
|
|
154
|
+
}
|
|
155
|
+
# Result: [state: "unlocked", coins: 1]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Hierarchical State Machine
|
|
159
|
+
|
|
160
|
+
Model nested states using path dispatch:
|
|
161
|
+
|
|
162
|
+
```rill
|
|
163
|
+
# Media player with play/pause substates
|
|
164
|
+
[
|
|
165
|
+
stopped: [
|
|
166
|
+
play: "playing.normal"
|
|
167
|
+
],
|
|
168
|
+
playing: [
|
|
169
|
+
normal: [
|
|
170
|
+
pause: "paused",
|
|
171
|
+
slow: "playing.slow",
|
|
172
|
+
fast: "playing.fast"
|
|
173
|
+
],
|
|
174
|
+
slow: [
|
|
175
|
+
pause: "paused",
|
|
176
|
+
normal: "playing.normal"
|
|
177
|
+
],
|
|
178
|
+
fast: [
|
|
179
|
+
pause: "paused",
|
|
180
|
+
normal: "playing.normal"
|
|
181
|
+
]
|
|
182
|
+
],
|
|
183
|
+
paused: [
|
|
184
|
+
play: "playing.normal",
|
|
185
|
+
stop: "stopped"
|
|
186
|
+
]
|
|
187
|
+
] :> $machine
|
|
188
|
+
|
|
189
|
+
# Parse hierarchical state
|
|
190
|
+
"playing.normal" :> $state
|
|
191
|
+
$state -> .split(".") :> $path
|
|
192
|
+
|
|
193
|
+
# Navigate to current state config
|
|
194
|
+
$path -> fold($machine) { $@.$ }
|
|
195
|
+
# Result: [pause: "paused", slow: "playing.slow", fast: "playing.fast"]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Dispatch Patterns
|
|
201
|
+
|
|
202
|
+
### Computed Routing
|
|
203
|
+
|
|
204
|
+
Route values through different processors based on type or content:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
# Conceptual - string keys with special chars require dispatch syntax
|
|
208
|
+
[
|
|
209
|
+
"application/json": |body|{ $body -> parse_json },
|
|
210
|
+
"text/plain": |body|{ $body -> .trim },
|
|
211
|
+
"text/csv": |body|{ $body -> .lines -> map { .split(",") } }
|
|
212
|
+
] :> $parsers
|
|
213
|
+
|
|
214
|
+
"application/json" :> $contentType
|
|
215
|
+
"{\"name\": \"test\"}" :> $body
|
|
216
|
+
|
|
217
|
+
$contentType -> $parsers -> |parser|{ $parser($body) }
|
|
218
|
+
# Result: [name: "test"]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Multi-Key Dispatch
|
|
222
|
+
|
|
223
|
+
Map multiple inputs to the same handler:
|
|
224
|
+
|
|
225
|
+
```rill
|
|
226
|
+
# HTTP method routing - correct multi-key syntax
|
|
227
|
+
[
|
|
228
|
+
["GET", "HEAD"]: [handler: "read", safe: true],
|
|
229
|
+
["POST", "PUT", "PATCH"]: [handler: "write", safe: false],
|
|
230
|
+
["DELETE"]: [handler: "delete", safe: false]
|
|
231
|
+
] :> $routes
|
|
232
|
+
|
|
233
|
+
"POST" -> $routes
|
|
234
|
+
# Result: [handler: "write", safe: false]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Default Handlers
|
|
238
|
+
|
|
239
|
+
Combine dispatch with `??` for fallback behavior:
|
|
240
|
+
|
|
241
|
+
```rill
|
|
242
|
+
[
|
|
243
|
+
success: |r|{ "Completed: {$r.data}" },
|
|
244
|
+
error: |r|{ "Failed: {$r.message}" },
|
|
245
|
+
pending: |r|{ "Waiting..." }
|
|
246
|
+
] :> $handlers
|
|
247
|
+
|
|
248
|
+
[status: "unknown", data: "test"] :> $response
|
|
249
|
+
|
|
250
|
+
$response.status -> $handlers ?? |r|{ "Unknown status: {$r.status}" }
|
|
251
|
+
-> |handler|{ $handler($response) }
|
|
252
|
+
# Result: "Unknown status: unknown"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Accumulator Patterns
|
|
258
|
+
|
|
259
|
+
### Running Statistics
|
|
260
|
+
|
|
261
|
+
Calculate statistics in a single pass:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
# Conceptual - dict spread [...$dict, key: val] not implemented
|
|
265
|
+
[23, 45, 12, 67, 34, 89, 56] :> $values
|
|
266
|
+
|
|
267
|
+
$values -> fold([sum: 0, count: 0, min: 999999, max: -999999]) {
|
|
268
|
+
[
|
|
269
|
+
sum: $@.sum + $,
|
|
270
|
+
count: $@.count + 1,
|
|
271
|
+
min: ($ < $@.min) ? $ ! $@.min,
|
|
272
|
+
max: ($ > $@.max) ? $ ! $@.max
|
|
273
|
+
]
|
|
274
|
+
} :> $stats
|
|
275
|
+
|
|
276
|
+
[...$stats, avg: $stats.sum / $stats.count]
|
|
277
|
+
# Result: [sum: 326, count: 7, min: 12, max: 89, avg: 46.57...]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Grouping
|
|
281
|
+
|
|
282
|
+
Group items by a computed key:
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
# Conceptual - uses dict spread and $$ syntax not implemented
|
|
286
|
+
[
|
|
287
|
+
[name: "Alice", dept: "Engineering"],
|
|
288
|
+
[name: "Bob", dept: "Sales"],
|
|
289
|
+
[name: "Carol", dept: "Engineering"],
|
|
290
|
+
[name: "Dave", dept: "Sales"]
|
|
291
|
+
] :> $employees
|
|
292
|
+
|
|
293
|
+
$employees -> fold([]) {
|
|
294
|
+
$@.?($$.dept) ? {
|
|
295
|
+
# Key exists - append to list
|
|
296
|
+
[...$@, ($$.dept): [...$@.($$.dept), $$.name]]
|
|
297
|
+
} ! {
|
|
298
|
+
# New key - create list
|
|
299
|
+
[...$@, ($$.dept): [$$.name]]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
# Result: [Engineering: ["Alice", "Carol"], Sales: ["Bob", "Dave"]]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Deduplication
|
|
306
|
+
|
|
307
|
+
Remove duplicates while preserving order:
|
|
308
|
+
|
|
309
|
+
```rill
|
|
310
|
+
["a", "b", "a", "c", "b", "d", "a"] :> $items
|
|
311
|
+
|
|
312
|
+
$items -> fold([seen: [], result: []]) {
|
|
313
|
+
$@.seen -> .has($) ? $@ ! {
|
|
314
|
+
[
|
|
315
|
+
seen: [...$@.seen, $],
|
|
316
|
+
result: [...$@.result, $]
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
} -> .result
|
|
320
|
+
# Result: ["a", "b", "c", "d"]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Control Flow Patterns
|
|
326
|
+
|
|
327
|
+
### Early Exit with Validation
|
|
328
|
+
|
|
329
|
+
Validate multiple conditions and exit on first failure:
|
|
330
|
+
|
|
331
|
+
```rill
|
|
332
|
+
[username: "", email: "test@", age: 15] :> $formData
|
|
333
|
+
|
|
334
|
+
$formData -> {
|
|
335
|
+
$.username -> .empty ? ([valid: false, err: "Username required"] -> return)
|
|
336
|
+
$.email -> .contains("@") -> ! ? ([valid: false, err: "Invalid email"] -> return)
|
|
337
|
+
($.age < 18) ? ([valid: false, err: "Must be 18+"] -> return)
|
|
338
|
+
[valid: true, data: $]
|
|
339
|
+
}
|
|
340
|
+
# Result: [valid: false, err: "Username required"]
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Retry with Backoff
|
|
344
|
+
|
|
345
|
+
Retry an operation with exponential backoff:
|
|
346
|
+
|
|
347
|
+
```rill
|
|
348
|
+
# Simulate flaky operation (host would provide real implementation)
|
|
349
|
+
|attempt|{
|
|
350
|
+
($attempt < 3) ? [ok: false, err: "Network error"] ! [ok: true, data: "Success"]
|
|
351
|
+
} :> $operation
|
|
352
|
+
|
|
353
|
+
# Retry loop with backoff
|
|
354
|
+
1 -> ($ <= 5) @ {
|
|
355
|
+
$operation($) :> $result
|
|
356
|
+
$result.ok ? ($result -> return)
|
|
357
|
+
log("Attempt {$} failed: {$result.err}")
|
|
358
|
+
$ + 1
|
|
359
|
+
} :> $final
|
|
360
|
+
|
|
361
|
+
$final.?ok ? $final ! [ok: false, err: "Max retries exceeded"]
|
|
362
|
+
# Result: [ok: true, data: "Success"]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Pipeline with Short-Circuit
|
|
366
|
+
|
|
367
|
+
Process steps that can fail at any point:
|
|
368
|
+
|
|
369
|
+
```rill
|
|
370
|
+
|input|{
|
|
371
|
+
[ok: true, value: $input -> .trim]
|
|
372
|
+
} :> $step1
|
|
373
|
+
|
|
374
|
+
|input|{
|
|
375
|
+
$input -> .len :> $len
|
|
376
|
+
($len < 3) ? [ok: false, err: "Too short"] ! [ok: true, value: $input -> .upper]
|
|
377
|
+
} :> $step2
|
|
378
|
+
|
|
379
|
+
|input|{
|
|
380
|
+
$input -> .contains("HELLO") :> $hasHello
|
|
381
|
+
$hasHello ? [ok: true, value: $input] ! [ok: false, err: "Must contain HELLO"]
|
|
382
|
+
} :> $step3
|
|
383
|
+
|
|
384
|
+
# Chain steps with early exit
|
|
385
|
+
" test input " :> $pipelineInput
|
|
386
|
+
$pipelineInput -> {
|
|
387
|
+
$step1($) :> $r1
|
|
388
|
+
($r1.ok == false) ? ($r1 -> return)
|
|
389
|
+
|
|
390
|
+
$step2($r1.value) :> $r2
|
|
391
|
+
($r2.ok == false) ? ($r2 -> return)
|
|
392
|
+
|
|
393
|
+
$step3($r2.value) :> $r3
|
|
394
|
+
$r3
|
|
395
|
+
}
|
|
396
|
+
# Result: [ok: false, err: "Must contain HELLO"]
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Data Transformation
|
|
402
|
+
|
|
403
|
+
### Flatten Nested Structure
|
|
404
|
+
|
|
405
|
+
Flatten arbitrarily nested lists:
|
|
406
|
+
|
|
407
|
+
```rill
|
|
408
|
+
# For known depth, chain operations
|
|
409
|
+
[[1, 2], [3, [4, 5]], [6]] :> $nested
|
|
410
|
+
|
|
411
|
+
# Flatten one level
|
|
412
|
+
$nested -> fold([]) { [...$@, ...$] }
|
|
413
|
+
# Result: [1, 2, 3, [4, 5], 6]
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Transpose Matrix
|
|
417
|
+
|
|
418
|
+
Convert rows to columns:
|
|
419
|
+
|
|
420
|
+
```rill
|
|
421
|
+
[
|
|
422
|
+
[1, 2, 3],
|
|
423
|
+
[4, 5, 6],
|
|
424
|
+
[7, 8, 9]
|
|
425
|
+
] :> $matrix
|
|
426
|
+
|
|
427
|
+
range(0, $matrix[0] -> .len) -> map |col|{
|
|
428
|
+
$matrix -> map |row|{ $row[$col] }
|
|
429
|
+
}
|
|
430
|
+
# Result: [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Zip Lists
|
|
434
|
+
|
|
435
|
+
Combine parallel lists into tuples:
|
|
436
|
+
|
|
437
|
+
```rill
|
|
438
|
+
["a", "b", "c"] :> $zipKeys
|
|
439
|
+
[1, 2, 3] :> $zipValues
|
|
440
|
+
|
|
441
|
+
range(0, $zipKeys -> .len) -> map |i|{
|
|
442
|
+
[$zipKeys[$i], $zipValues[$i]]
|
|
443
|
+
}
|
|
444
|
+
# Result: [["a", 1], ["b", 2], ["c", 3]]
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Converting to a dict requires dict spread (not yet implemented):
|
|
448
|
+
|
|
449
|
+
```text
|
|
450
|
+
# Conceptual - dict spread [...$@, (key): val] not implemented
|
|
451
|
+
range(0, $zipKeys -> .len) -> fold([]) |i|{
|
|
452
|
+
[...$@, ($zipKeys[$i]): $zipValues[$i]]
|
|
453
|
+
}
|
|
454
|
+
# Result: [a: 1, b: 2, c: 3]
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## String Processing
|
|
460
|
+
|
|
461
|
+
### Template Expansion
|
|
462
|
+
|
|
463
|
+
Simple template with variable substitution (using angle brackets as delimiters):
|
|
464
|
+
|
|
465
|
+
```rill
|
|
466
|
+
"Hello <name>, your order <orderId> ships on <date>." :> $template
|
|
467
|
+
|
|
468
|
+
[name: "Alice", orderId: "12345", date: "2024-03-15"] :> $templateVars
|
|
469
|
+
|
|
470
|
+
$templateVars -> .entries -> fold($template) {
|
|
471
|
+
$@.replace_all("<{$[0]}>", $[1] -> .str)
|
|
472
|
+
}
|
|
473
|
+
# Result: "Hello Alice, your order 12345 ships on 2024-03-15."
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Parse Key-Value Pairs
|
|
477
|
+
|
|
478
|
+
Extract structured data from formatted text:
|
|
479
|
+
|
|
480
|
+
```text
|
|
481
|
+
# Conceptual - uses dict spread with computed keys
|
|
482
|
+
"name=Alice;age=30;city=Seattle" :> $input
|
|
483
|
+
|
|
484
|
+
$input
|
|
485
|
+
-> .split(";")
|
|
486
|
+
-> fold([]) {
|
|
487
|
+
$ -> .split("=") -> *<$key, $value>
|
|
488
|
+
[...$@, ($key): $value]
|
|
489
|
+
}
|
|
490
|
+
# Result: [name: "Alice", age: "30", city: "Seattle"]
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Word Frequency
|
|
494
|
+
|
|
495
|
+
Count word occurrences:
|
|
496
|
+
|
|
497
|
+
```text
|
|
498
|
+
# Conceptual - uses dynamic existence check and dict spread
|
|
499
|
+
"the quick brown fox jumps over the lazy dog the fox" :> $text
|
|
500
|
+
|
|
501
|
+
$text
|
|
502
|
+
-> .lower
|
|
503
|
+
-> .split(" ")
|
|
504
|
+
-> fold([]) {
|
|
505
|
+
$@.?$
|
|
506
|
+
? [...$@, ($): $@.$ + 1]
|
|
507
|
+
! [...$@, ($): 1]
|
|
508
|
+
}
|
|
509
|
+
# Result: [the: 3, quick: 1, brown: 1, fox: 2, jumps: 1, over: 1, lazy: 1, dog: 1]
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Closure Patterns
|
|
515
|
+
|
|
516
|
+
### Partial Application
|
|
517
|
+
|
|
518
|
+
Create specialized functions from general ones:
|
|
519
|
+
|
|
520
|
+
```rill
|
|
521
|
+
# General formatter
|
|
522
|
+
|prefix, suffix, value|{
|
|
523
|
+
"{$prefix}{$value}{$suffix}"
|
|
524
|
+
} :> $format
|
|
525
|
+
|
|
526
|
+
# Partial application via closure
|
|
527
|
+
|value|{ $format("[", "]", $value) } :> $bracket
|
|
528
|
+
|value|{ $format("<", ">", $value) } :> $angle
|
|
529
|
+
|
|
530
|
+
$bracket("test") # "[test]"
|
|
531
|
+
$angle("html") # "<html>"
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Memoization Pattern
|
|
535
|
+
|
|
536
|
+
Cache expensive computations:
|
|
537
|
+
|
|
538
|
+
```rill
|
|
539
|
+
# Build cache alongside computation
|
|
540
|
+
|n, cache|{
|
|
541
|
+
$cache.?($n -> .str) ? $cache.($n -> .str) ! {
|
|
542
|
+
# Compute fibonacci
|
|
543
|
+
($n <= 1) ? $n ! {
|
|
544
|
+
$n - 1 -> |prev|{ |prev, cache|{ ... }($prev, $cache) } :> $a # Simplified
|
|
545
|
+
# Real memoization requires host support for mutable cache
|
|
546
|
+
$a + $n - 2
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Note: True memoization with persistent cache requires host-provided storage since rill values are immutable.
|
|
553
|
+
|
|
554
|
+
### Composition
|
|
555
|
+
|
|
556
|
+
Combine functions into pipelines:
|
|
557
|
+
|
|
558
|
+
```rill
|
|
559
|
+
|f, g|{
|
|
560
|
+
|x|{ $x -> $f() -> $g() }
|
|
561
|
+
} :> $compose
|
|
562
|
+
|
|
563
|
+
|x|{ $x * 2 } :> $double
|
|
564
|
+
|x|{ $x + 1 } :> $increment
|
|
565
|
+
|
|
566
|
+
$compose($double, $increment) :> $doubleThenIncrement
|
|
567
|
+
|
|
568
|
+
5 -> $doubleThenIncrement()
|
|
569
|
+
# Result: 11
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Validation Patterns
|
|
575
|
+
|
|
576
|
+
### Schema Validation
|
|
577
|
+
|
|
578
|
+
Validate dict structure against rules:
|
|
579
|
+
|
|
580
|
+
```text
|
|
581
|
+
# Conceptual - uses dynamic existence checks and dict spread
|
|
582
|
+
[
|
|
583
|
+
name: [type: "string", required: true, minLen: 1],
|
|
584
|
+
age: [type: "number", required: true, min: 0, max: 150],
|
|
585
|
+
email: [type: "string", required: false]
|
|
586
|
+
] :> $schema
|
|
587
|
+
|
|
588
|
+
[name: "Alice", age: 200] :> $data
|
|
589
|
+
|
|
590
|
+
$schema.entries -> fold([valid: true, errors: []]) {
|
|
591
|
+
$[0] :> $field
|
|
592
|
+
$[1] :> $rules
|
|
593
|
+
|
|
594
|
+
# Check required
|
|
595
|
+
($rules.required && ($data.?($field) -> !)) ? {
|
|
596
|
+
[valid: false, errors: [...$@.errors, "{$field} is required"]]
|
|
597
|
+
} ! {
|
|
598
|
+
# Check type and constraints if field exists
|
|
599
|
+
$data.?($field) ? {
|
|
600
|
+
$data.($field) :> $value
|
|
601
|
+
|
|
602
|
+
# Type check
|
|
603
|
+
(type($value) != $rules.type) ? {
|
|
604
|
+
[valid: false, errors: [...$@.errors, "{$field} must be {$rules.type}"]]
|
|
605
|
+
} ! {
|
|
606
|
+
# Range check for numbers
|
|
607
|
+
($rules.type == "number") ? {
|
|
608
|
+
($rules.?min && $value < $rules.min) ? {
|
|
609
|
+
[valid: false, errors: [...$@.errors, "{$field} below minimum"]]
|
|
610
|
+
} ! ($rules.?max && $value > $rules.max) ? {
|
|
611
|
+
[valid: false, errors: [...$@.errors, "{$field} above maximum"]]
|
|
612
|
+
} ! $@
|
|
613
|
+
} ! $@
|
|
614
|
+
}
|
|
615
|
+
} ! $@
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
# Result: [valid: false, errors: ["age above maximum"]]
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## See Also
|
|
624
|
+
|
|
625
|
+
- [Reference](11_reference.md) — Complete language specification
|
|
626
|
+
- [Collections](07_collections.md) — `each`, `map`, `filter`, `fold` details
|
|
627
|
+
- [Closures](06_closures.md) — Function patterns and binding
|
|
628
|
+
- [Parsing](10_parsing.md) — Text extraction utilities
|