beck_core/prelude.rs
1//! The standard library of the walking skeleton.
2//!
3//! Small on purpose. §3.2's promise is that "effect polymorphism is what keeps one standard
4//! library" — `map : (list[a], (a -> b ! e)) -> list[b] ! e`. Phase 2 has effect rows, so that
5//! signature is now written as written: `map_list` is polymorphic in what its function argument
6//! does, and mapping an effectful function over a list is effectful *in exactly that way*. One
7//! library, one definition per operation, usable from any tier the placement solver allows.
8//!
9//! The rows here are the source of truth for inference. [`Prim::effects`] is the same information
10//! for the atoms a primitive performs *itself*, and a test holds the two in agreement.
11//!
12//! Everything here is a [`Prim`], which means the evaluator implements it and the eventual
13//! Cranelift/LLVM backends implement it — never a Beck-source shim that would have to be compiled
14//! twice.
15
16use std::collections::BTreeMap;
17use std::sync::Arc;
18
19use crate::core::Prim;
20use crate::ty::{Effect, MethodSig, Row, RowVarId, Scheme, TraitSig, Ty, TyDecl, Variant};
21
22/// A fresh type variable id for a scheme. Scheme variables are numbered from a private range that
23/// never collides with the inference variables `Subst` mints, because `instantiate` replaces them.
24const A: u32 = 1_000_000;
25const B: u32 = 1_000_001;
26const C: u32 = 1_000_002;
27
28/// Row-variable ids for the schemes below, in their own range for the same reason `A`/`B`/`C` are:
29/// `instantiate` replaces them, so they can never collide with an inference variable.
30const E: RowVarId = 2_000_000;
31
32fn v(id: u32) -> Ty {
33 Ty::Var(id)
34}
35
36fn poly(vars: &[u32], ty: Ty) -> Scheme {
37 Scheme {
38 vars: vars.to_vec(),
39 row_vars: Vec::new(),
40 params: Vec::new(),
41 ty,
42 }
43}
44
45/// A scheme polymorphic in both dimensions — §3.2's `(list[a], (a -> b ! e)) -> list[b] ! e`.
46fn poly_eff(vars: &[u32], row_vars: &[RowVarId], ty: Ty) -> Scheme {
47 Scheme {
48 vars: vars.to_vec(),
49 row_vars: row_vars.to_vec(),
50 params: Vec::new(),
51 ty,
52 }
53}
54
55/// A pure function type.
56fn fun(params: Vec<Ty>, ret: Ty) -> Ty {
57 Ty::fun(params, ret)
58}
59
60/// A function type with an effect row.
61fn fun_eff(params: Vec<Ty>, ret: Ty, row: Row) -> Ty {
62 Ty::fun_eff(params, ret, row)
63}
64
65/// Every primitive's name and type.
66pub fn prims() -> Vec<(&'static str, Prim, Scheme)> {
67 let int = Ty::int();
68 let bool_ = Ty::bool_();
69 let str_ = Ty::str_();
70 let float = Ty::con(Ty::FLOAT);
71 let html = Ty::html();
72 let attr = Ty::con(Ty::ATTR);
73
74 vec![
75 // `+` is resolved bidirectionally in `check` so that it can also concatenate strings
76 // without introducing a numeric type class; the scheme here is its Int form.
77 (
78 "+",
79 Prim::Add,
80 Scheme::mono(fun(vec![int.clone(), int.clone()], int.clone())),
81 ),
82 (
83 "-",
84 Prim::Sub,
85 Scheme::mono(fun(vec![int.clone(), int.clone()], int.clone())),
86 ),
87 (
88 "*",
89 Prim::Mul,
90 Scheme::mono(fun(vec![int.clone(), int.clone()], int.clone())),
91 ),
92 (
93 "/",
94 Prim::Div,
95 Scheme::mono(fun(vec![int.clone(), int.clone()], int.clone())),
96 ),
97 (
98 "%",
99 Prim::Rem,
100 Scheme::mono(fun(vec![int.clone(), int.clone()], int.clone())),
101 ),
102 // The reals. `abs` is written for both tiers in SICP and is resolved from its operand in
103 // `check`, exactly as `+` is; the scheme here is its `Int` form, which is what a reference
104 // to it *as a value* gets (`docs/27` §27.2).
105 (
106 "abs",
107 Prim::Abs,
108 Scheme::mono(fun(vec![int.clone()], int.clone())),
109 ),
110 (
111 "sqrt",
112 Prim::Sqrt,
113 Scheme::mono(fun(vec![float.clone()], float.clone())),
114 ),
115 // The two trigonometric functions, and the conversion back down from a real.
116 //
117 // `docs/27` built the reals with `sqrt` and nothing else, and three phases of programs
118 // never asked for more — until a benchmark did. `Math.sin`, `Math.cos` and a `(int)` cast
119 // are what Are We Fast Yet's collision detector is written in, and a numeric tower with a
120 // square root and no sine is a gap rather than a decision (`docs/53` §53.5).
121 //
122 // All three are primitives in `lib/README.md`'s sense — composition would be a slower,
123 // less correct copy, and unlike `money.beck`'s arithmetic there is nothing here that Beck
124 // *should* be expressing — but only truncation is the *host's*. `sin` and `cos` are
125 // computed in the runtime library and correctly rounded, because IEEE 754 pins neither and
126 // a fold that reached the platform's libm would replay differently on another machine
127 // (`beck_prim::math`, `adr/0031`).
128 (
129 "sin",
130 Prim::Sin,
131 Scheme::mono(fun(vec![float.clone()], float.clone())),
132 ),
133 (
134 "cos",
135 Prim::Cos,
136 Scheme::mono(fun(vec![float.clone()], float.clone())),
137 ),
138 // Toward zero, which is what every language with this conversion means by it and what
139 // IEEE 754 calls `roundTowardZero`. Saturating rather than wrapping at the ends of `Int`,
140 // because a wrap would turn a large real into a small integer of the wrong sign.
141 (
142 "trunc",
143 Prim::Trunc,
144 Scheme::mono(fun(vec![float.clone()], int.clone())),
145 ),
146 (
147 "float",
148 Prim::ToFloat,
149 Scheme::mono(fun(vec![int.clone()], float.clone())),
150 ),
151 (
152 "negate",
153 Prim::Neg,
154 Scheme::mono(fun(vec![int.clone()], int.clone())),
155 ),
156 (
157 "==",
158 Prim::Eq,
159 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
160 ),
161 (
162 "!=",
163 Prim::Ne,
164 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
165 ),
166 (
167 "<",
168 Prim::Lt,
169 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
170 ),
171 (
172 "<=",
173 Prim::Le,
174 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
175 ),
176 (
177 ">",
178 Prim::Gt,
179 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
180 ),
181 (
182 ">=",
183 Prim::Ge,
184 poly(&[A], fun(vec![v(A), v(A)], bool_.clone())),
185 ),
186 (
187 "and",
188 Prim::And,
189 Scheme::mono(fun(vec![bool_.clone(), bool_.clone()], bool_.clone())),
190 ),
191 (
192 "or",
193 Prim::Or,
194 Scheme::mono(fun(vec![bool_.clone(), bool_.clone()], bool_.clone())),
195 ),
196 (
197 "not",
198 Prim::Not,
199 Scheme::mono(fun(vec![bool_.clone()], bool_.clone())),
200 ),
201 (
202 "str",
203 Prim::ToStr,
204 poly(&[A], fun(vec![v(A)], str_.clone())),
205 ),
206 (
207 "str_trim",
208 Prim::StrTrim,
209 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
210 ),
211 // The canonical fallible operation, and the reason it is here rather than in the standard
212 // library Wave 2 will write: `corpus/29-fallible.beck` needs one thing that can genuinely
213 // fail on its input, and a parse is that thing in every language.
214 (
215 "str_to_int",
216 Prim::StrToInt,
217 Scheme::mono(fun(vec![str_.clone()], Ty::option(Ty::int()))),
218 ),
219 // ------------------------------------------------------------------------ strings
220 //
221 // Wave 2's string half ([`docs/08`](../../../../../docs/08-roadmap.md) §8.5.4). Every one of
222 // these is a primitive rather than a definition written in Beck, and the reason is the
223 // same in each case: a string is where the host has to be asked. `str_upper` is a Unicode
224 // table, `str_split` is an allocation strategy, and writing either of them over a
225 // `list[Str]` of characters in Beck would be a slower, less correct copy of what the host
226 // already has. Where there *is* something to express — a `Decimal`, a `Json` document —
227 // Wave 2 writes it in Beck instead, which is the distinction §1.1 claims to be able to
228 // make.
229 //
230 // Positions are counted in **characters** — Unicode scalar values — and `str_len`,
231 // `str_slice` and `str_index_of` are one unit or they are a trap;
232 // `stdlib.rs::string_positions_are_characters_everywhere_or_nowhere` is where that is held.
233 //
234 // `str_slice(s, start, count)` takes a **count**, not an end index. Worth stating because
235 // the signature cannot: a primitive's parameters have no names in the generated reference,
236 // so `(Str, Int, Int) -> Str` reads either way and the first caller to pass a non-zero
237 // start with a real count got it wrong (`docs/46` §46.8).
238 //
239 // Both are clamped rather than refused: a slice past the end is the empty string, not a
240 // failure. That is a decision and not an oversight — a slice is not a parse, and `raises`
241 // is for a program's own vocabulary rather than for the standard library's arithmetic
242 // ([`27`](../../../../../docs/27-the-walls-come-down-report.md)).
243 (
244 "str_len",
245 Prim::StrLen,
246 Scheme::mono(fun(vec![str_.clone()], int.clone())),
247 ),
248 (
249 "str_slice",
250 Prim::StrSlice,
251 Scheme::mono(fun(
252 vec![str_.clone(), int.clone(), int.clone()],
253 str_.clone(),
254 )),
255 ),
256 (
257 "str_split",
258 Prim::StrSplit,
259 Scheme::mono(fun(
260 vec![str_.clone(), str_.clone()],
261 Ty::list(str_.clone()),
262 )),
263 ),
264 (
265 "str_join",
266 Prim::StrJoin,
267 Scheme::mono(fun(
268 vec![Ty::list(str_.clone()), str_.clone()],
269 str_.clone(),
270 )),
271 ),
272 (
273 "str_contains",
274 Prim::StrContains,
275 Scheme::mono(fun(vec![str_.clone(), str_.clone()], bool_.clone())),
276 ),
277 (
278 "str_starts_with",
279 Prim::StrStartsWith,
280 Scheme::mono(fun(vec![str_.clone(), str_.clone()], bool_.clone())),
281 ),
282 (
283 "str_ends_with",
284 Prim::StrEndsWith,
285 Scheme::mono(fun(vec![str_.clone(), str_.clone()], bool_.clone())),
286 ),
287 (
288 "str_upper",
289 Prim::StrUpper,
290 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
291 ),
292 (
293 "str_lower",
294 Prim::StrLower,
295 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
296 ),
297 (
298 "str_replace",
299 Prim::StrReplace,
300 Scheme::mono(fun(
301 vec![str_.clone(), str_.clone(), str_.clone()],
302 str_.clone(),
303 )),
304 ),
305 (
306 "str_index_of",
307 Prim::StrIndexOf,
308 Scheme::mono(fun(
309 vec![str_.clone(), str_.clone()],
310 Ty::option(int.clone()),
311 )),
312 ),
313 (
314 "str_repeat",
315 Prim::StrRepeat,
316 Scheme::mono(fun(vec![str_.clone(), int.clone()], str_.clone())),
317 ),
318 (
319 "str_chars",
320 Prim::StrChars,
321 Scheme::mono(fun(vec![str_.clone()], Ty::list(str_.clone()))),
322 ),
323 // ------------------------------------------------------------------------ collections
324 //
325 // The higher-order ones are row-polymorphic in the argument's effects — §3.2's
326 // `map : (list[a], (a -> b ! e)) -> list[b] ! e`, which
327 // [`27`](../../../../../docs/27-the-walls-come-down-report.md) made true of
328 // a *user's* definitions too. A pure caller of `list_fold` stays pure however another
329 // caller uses it.
330 (
331 "list_get",
332 Prim::ListGet,
333 poly(
334 &[A],
335 fun(vec![Ty::list(v(A)), int.clone()], Ty::option(v(A))),
336 ),
337 ),
338 (
339 "list_slice",
340 Prim::ListSlice,
341 poly(
342 &[A],
343 fun(
344 vec![Ty::list(v(A)), int.clone(), int.clone()],
345 Ty::list(v(A)),
346 ),
347 ),
348 ),
349 (
350 "list_reverse",
351 Prim::ListReverse,
352 poly(&[A], fun(vec![Ty::list(v(A))], Ty::list(v(A)))),
353 ),
354 (
355 "list_take",
356 Prim::ListTake,
357 poly(&[A], fun(vec![Ty::list(v(A)), int.clone()], Ty::list(v(A)))),
358 ),
359 (
360 "list_drop",
361 Prim::ListDrop,
362 poly(&[A], fun(vec![Ty::list(v(A)), int.clone()], Ty::list(v(A)))),
363 ),
364 (
365 "list_contains",
366 Prim::ListContains,
367 poly(&[A], fun(vec![Ty::list(v(A)), v(A)], bool_.clone())),
368 ),
369 (
370 "list_index_of",
371 Prim::ListIndexOf,
372 poly(
373 &[A],
374 fun(vec![Ty::list(v(A)), v(A)], Ty::option(int.clone())),
375 ),
376 ),
377 (
378 "list_append",
379 Prim::ListAppend,
380 poly(&[A], fun(vec![Ty::list(v(A)), v(A)], Ty::list(v(A)))),
381 ),
382 // Zip *with* a function rather than zip into a pair: Beck has no tuple type, and inventing
383 // one for this would be a language change hiding inside a library addition. The shorter
384 // list decides the length, which is the convention every language that has this agrees on.
385 (
386 "list_zip_with",
387 Prim::ListZip,
388 poly_eff(
389 &[A, B, C],
390 &[E],
391 fun_eff(
392 vec![
393 Ty::list(v(A)),
394 Ty::list(v(B)),
395 fun_eff(vec![v(A), v(B)], v(C), Row::var(E)),
396 ],
397 Ty::list(v(C)),
398 Row::var(E),
399 ),
400 ),
401 ),
402 (
403 "list_fold",
404 Prim::ListFold,
405 poly_eff(
406 &[A, B],
407 &[E],
408 fun_eff(
409 vec![
410 Ty::list(v(A)),
411 v(B),
412 fun_eff(vec![v(B), v(A)], v(B), Row::var(E)),
413 ],
414 v(B),
415 Row::var(E),
416 ),
417 ),
418 ),
419 (
420 "list_all",
421 Prim::ListAll,
422 poly_eff(
423 &[A],
424 &[E],
425 fun_eff(
426 vec![
427 Ty::list(v(A)),
428 fun_eff(vec![v(A)], bool_.clone(), Row::var(E)),
429 ],
430 bool_.clone(),
431 Row::var(E),
432 ),
433 ),
434 ),
435 (
436 "list_any",
437 Prim::ListAny,
438 poly_eff(
439 &[A],
440 &[E],
441 fun_eff(
442 vec![
443 Ty::list(v(A)),
444 fun_eff(vec![v(A)], bool_.clone(), Row::var(E)),
445 ],
446 bool_.clone(),
447 Row::var(E),
448 ),
449 ),
450 ),
451 (
452 "list_flat_map",
453 Prim::ListFlatMap,
454 poly_eff(
455 &[A, B],
456 &[E],
457 fun_eff(
458 vec![
459 Ty::list(v(A)),
460 fun_eff(vec![v(A)], Ty::list(v(B)), Row::var(E)),
461 ],
462 Ty::list(v(B)),
463 Row::var(E),
464 ),
465 ),
466 ),
467 (
468 "map_keys",
469 Prim::MapKeys,
470 poly(&[A, B], fun(vec![Ty::map(v(A), v(B))], Ty::list(v(A)))),
471 ),
472 (
473 "map_merge",
474 Prim::MapMerge,
475 poly(
476 &[A, B],
477 fun(
478 vec![Ty::map(v(A), v(B)), Ty::map(v(A), v(B))],
479 Ty::map(v(A), v(B)),
480 ),
481 ),
482 ),
483 // ------------------------------------------------------------------------ JSON and time
484 //
485 // `json_parse` and `time_parse` **raise** rather than returning a `Result`, and that is the
486 // whole reason [`08`](../../../../../docs/08-roadmap.md) §8.5.3's trap 2 said the standard
487 // library had to wait for [`27`](../../../../../docs/27-the-walls-come-down-report.md). A caller who
488 // wants a `Result` writes `try:`; a caller already inside something fallible writes
489 // nothing. Had these been written first, every one of their signatures would have had to
490 // change.
491 (
492 "json_parse",
493 Prim::JsonParse,
494 Scheme::mono(fun_eff(
495 vec![str_.clone()],
496 Ty::con("Json"),
497 Row::of([Effect::Raises(Arc::from("JsonError"))]),
498 )),
499 ),
500 (
501 "json_render",
502 Prim::JsonRender,
503 Scheme::mono(fun(vec![Ty::con("Json")], str_.clone())),
504 ),
505 // RFC 3339 in UTC, and only that: a time zone is a database with a release schedule, and
506 // one is not being embedded in a compiler on the way past. `now()` gives the milliseconds
507 // these two are the calendar over.
508 (
509 "time_format",
510 Prim::TimeFormat,
511 Scheme::mono(fun(vec![int.clone()], str_.clone())),
512 ),
513 (
514 "time_parse",
515 Prim::TimeParse,
516 Scheme::mono(fun_eff(
517 vec![str_.clone()],
518 int.clone(),
519 Row::of([Effect::Raises(Arc::from("TimeError"))]),
520 )),
521 ),
522 // ------------------------------------------------- digests, encodings and identifiers
523 //
524 // Wave 2's crypto item, host half. A hash function is a table and base64 is a grammar, so
525 // both are here rather than in `lib/`; what a program *does* with a digest — a token, a
526 // fingerprint, a check that reads two halves apart — is `lib/crypto.beck`.
527 //
528 // A digest is **pure**. That is the line between this group and `uuid()`/`now()`, which are
529 // the two nondeterministic things a crypto library is usually asked for: the same input
530 // digests to the same string on every replay, so nothing here has to be recorded on an
531 // envelope, and §3.7's rule about folds does not reach it.
532 (
533 "digest",
534 Prim::Digest,
535 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
536 ),
537 // The one function whose input is a `secret[Str]` and whose output is a `Str`.
538 //
539 // A message authentication code exists to be given to somebody who must not learn the key,
540 // so the declassification is what the operation *is* rather than a hole in §3.5. It is
541 // charged `cap.sign` for the reason `reveal` is charged `cap.internal`: no client tier
542 // discharges a capability, so a view cannot mint a token, and a server that mints one has
543 // said so in its row. `adr/0014` is the decision and `security.rs` is the gate that keeps
544 // this the *only* one.
545 (
546 "digest_keyed",
547 Prim::DigestKeyed,
548 Scheme::mono(fun_eff(
549 vec![Ty::secret(str_.clone()), str_.clone()],
550 str_.clone(),
551 Row::of([Effect::Cap(Arc::from("sign"))]),
552 )),
553 ),
554 // Comparing a digest with `==` returns at the first differing byte, which tells whoever is
555 // guessing how much of their guess was right. This does not.
556 (
557 "digest_eq",
558 Prim::DigestEq,
559 Scheme::mono(fun(vec![str_.clone(), str_.clone()], bool_.clone())),
560 ),
561 (
562 "hex_encode",
563 Prim::HexEncode,
564 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
565 ),
566 (
567 "hex_decode",
568 Prim::HexDecode,
569 Scheme::mono(fun_eff(
570 vec![str_.clone()],
571 str_.clone(),
572 Row::of([Effect::Raises(Arc::from("EncodingError"))]),
573 )),
574 ),
575 // RFC 4648 §5 — the URL-safe alphabet, unpadded — because every place a Beck program puts
576 // one of these is a place `+`, `/` and `=` have to be escaped.
577 (
578 "base64_encode",
579 Prim::Base64Encode,
580 Scheme::mono(fun(vec![str_.clone()], str_.clone())),
581 ),
582 (
583 "base64_decode",
584 Prim::Base64Decode,
585 Scheme::mono(fun_eff(
586 vec![str_.clone()],
587 str_.clone(),
588 Row::of([Effect::Raises(Arc::from("EncodingError"))]),
589 )),
590 ),
591 // `uuid()` has minted one since Phase 1 and nothing has ever read one back. This
592 // *normalises* rather than only validating: two spellings of one identifier must not be
593 // two map keys, and a `Str` that has been through here is canonical.
594 (
595 "uuid_parse",
596 Prim::UuidParse,
597 Scheme::mono(fun_eff(
598 vec![str_.clone()],
599 str_.clone(),
600 Row::of([Effect::Raises(Arc::from("UuidError"))]),
601 )),
602 ),
603 (
604 "uuid_version",
605 Prim::UuidVersion,
606 Scheme::mono(fun_eff(
607 vec![str_.clone()],
608 int.clone(),
609 Row::of([Effect::Raises(Arc::from("UuidError"))]),
610 )),
611 ),
612 // ------------------------------------------------------------------------ the outbound call
613 //
614 // The row here is half of the truth, and the half that is a constant. `net.out(host)` is
615 // charged at the *call site* from the literal first argument (`check::prim_call`), because
616 // an effect atom whose argument is a value is the one thing this language has no way to
617 // write in a scheme — and because the egress policy §6.5 derives is exactly the set of
618 // those atoms, so a host that were not written where the call is would not be derivable.
619 (
620 "http_fetch",
621 Prim::HttpFetch,
622 Scheme::mono(fun_eff(
623 vec![str_.clone(), Ty::con("HttpRequest")],
624 Ty::con("HttpResponse"),
625 Row::of([Effect::Raises(Arc::from("HttpError"))]),
626 )),
627 ),
628 (
629 "str_is_empty",
630 Prim::StrIsEmpty,
631 Scheme::mono(fun(vec![str_.clone()], bool_.clone())),
632 ),
633 (
634 "list_len",
635 Prim::ListLen,
636 poly(&[A], fun(vec![Ty::list(v(A))], int.clone())),
637 ),
638 (
639 "list_is_empty",
640 Prim::ListIsEmpty,
641 poly(&[A], fun(vec![Ty::list(v(A))], bool_.clone())),
642 ),
643 // The two aggregates that have no comparator, for the same reason `sort_by` takes a *key*
644 // rather than one: [`crate::Value`]'s order is total over every value a program can build
645 // ([`docs/54`](../../../../../docs/54-ordering.md)), so the smallest of a list is a
646 // question that needs nothing from the caller. A caller that wants the smallest *by*
647 // something sorts by that key and takes the head, which is what `lib/collections.beck`
648 // offers over these.
649 //
650 // `Option` rather than a failure: an empty list has no smallest element and that is an
651 // ordinary answer rather than an error, which is `list_get`'s decision one line up.
652 (
653 "list_min",
654 Prim::ListMin,
655 poly(&[A], fun(vec![Ty::list(v(A))], Ty::option(v(A)))),
656 ),
657 (
658 "list_max",
659 Prim::ListMax,
660 poly(&[A], fun(vec![Ty::list(v(A))], Ty::option(v(A)))),
661 ),
662 // **A sum is its answer, not the order it was added in** — which is the whole of the
663 // decision [`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.9
664 // item 6 said had to be taken before an operator could be written. `list_sum` is the exact
665 // sum of the list and raises when *that* does not fit an `Int`, so it is a function of the
666 // multiset of its elements and of nothing else: a running total maintained by adding what
667 // arrived and subtracting what left is the same number, and fails on the same lists.
668 //
669 // That makes it a **conservative extension** of the `+` above rather than a rival to it.
670 // Where `x1 + x2 + …` has an answer this is the same answer; where the fold raises on the
671 // way to a total that fits — `[Int_MAX, Int_MAX, -Int_MAX]` — this one has it. Nothing that
672 // held before holds differently.
673 //
674 // **`Int` only, and that is a decision.** The same definition over `Float` would not be an
675 // extension of the fold, it would disagree with it: an order-independent float sum is a
676 // *different number* in the last bits, on ordinary inputs, so a program with one of each in
677 // it would have two answers to one question. A float total stays the fold a program writes,
678 // and `beck explain cost` prices it as the recompute it is (§46.16).
679 (
680 "list_sum",
681 Prim::ListSum,
682 Scheme::mono(fun(vec![Ty::list(int.clone())], int.clone())),
683 ),
684 // **The same list with later duplicates dropped**, which is `lib/collections.beck`'s
685 // `unique` and not a third answer beside it: that function's body is now a call to this
686 // one, so nothing in the language means anything different from what it meant before. The
687 // order is the *input's* — the first occurrence of each value stays where it was — which is
688 // what separates it from `elements(set_of(xs))`, the library's other duplicate-free list
689 // and a **sorted** one. Both are wanted and neither is the other, so this primitive takes
690 // the answer a program already had rather than inventing one
691 // ([`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.9 item 7).
692 //
693 // **Why a primitive at all**, when a dedup is composition and `lib/README.md`'s division
694 // says composition is written in Beck: because the view engine has to *recognise* it, which
695 // is the one other case that division admits ([`docs/46`](../../../../../docs/46-standard-library-report.md)
696 // §46.16). A fold is opaque to [`crate::plan`], so a page showing the values in use would
697 // be rebuilt from the whole collection on every event however the fold was written.
698 (
699 "list_unique",
700 Prim::ListUnique,
701 poly(&[A], fun(vec![Ty::list(v(A))], Ty::list(v(A)))),
702 ),
703 // §3.2, verbatim: `map : (list[a], (a -> b ! e)) -> list[b] ! e`. Mapping a function that
704 // touches the dom over a list touches the dom; mapping a pure one does not.
705 (
706 "map_list",
707 Prim::MapList,
708 poly_eff(
709 &[A, B],
710 &[E],
711 fun_eff(
712 vec![Ty::list(v(A)), fun_eff(vec![v(A)], v(B), Row::var(E))],
713 Ty::list(v(B)),
714 Row::var(E),
715 ),
716 ),
717 ),
718 (
719 "filter_list",
720 Prim::FilterList,
721 poly_eff(
722 &[A],
723 &[E],
724 fun_eff(
725 vec![
726 Ty::list(v(A)),
727 fun_eff(vec![v(A)], bool_.clone(), Row::var(E)),
728 ],
729 Ty::list(v(A)),
730 Row::var(E),
731 ),
732 ),
733 ),
734 (
735 "concat_lists",
736 Prim::ConcatLists,
737 poly(&[A], fun(vec![Ty::list(Ty::list(v(A)))], Ty::list(v(A)))),
738 ),
739 (
740 "sort_by",
741 Prim::SortBy,
742 poly_eff(
743 &[A, B],
744 &[E],
745 fun_eff(
746 vec![Ty::list(v(A)), fun_eff(vec![v(A)], v(B), Row::var(E))],
747 Ty::list(v(A)),
748 Row::var(E),
749 ),
750 ),
751 ),
752 (
753 "map_get",
754 Prim::MapGet,
755 poly(
756 &[A, B],
757 fun(vec![Ty::map(v(A), v(B)), v(A)], Ty::option(v(B))),
758 ),
759 ),
760 (
761 "map_insert",
762 Prim::MapInsert,
763 poly(
764 &[A, B],
765 fun(vec![Ty::map(v(A), v(B)), v(A), v(B)], Ty::map(v(A), v(B))),
766 ),
767 ),
768 (
769 "map_remove",
770 Prim::MapRemove,
771 poly(
772 &[A, B],
773 fun(vec![Ty::map(v(A), v(B)), v(A)], Ty::map(v(A), v(B))),
774 ),
775 ),
776 (
777 "map_values",
778 Prim::MapValues,
779 poly(&[A, B], fun(vec![Ty::map(v(A), v(B))], Ty::list(v(B)))),
780 ),
781 (
782 "map_contains",
783 Prim::MapContains,
784 poly(&[A, B], fun(vec![Ty::map(v(A), v(B)), v(A)], bool_.clone())),
785 ),
786 (
787 "map_len",
788 Prim::MapLen,
789 poly(&[A, B], fun(vec![Ty::map(v(A), v(B))], int.clone())),
790 ),
791 (
792 "is_some",
793 Prim::OptionIsSome,
794 poly(&[A], fun(vec![Ty::option(v(A))], bool_.clone())),
795 ),
796 (
797 "unwrap_or",
798 Prim::OptionUnwrapOr,
799 poly(&[A], fun(vec![Ty::option(v(A)), v(A)], v(A))),
800 ),
801 (
802 "html_el",
803 Prim::HtmlEl,
804 Scheme::mono(fun(
805 vec![str_.clone(), Ty::list(attr.clone()), Ty::list(html.clone())],
806 html.clone(),
807 )),
808 ),
809 (
810 "html_text",
811 Prim::HtmlText,
812 poly(&[A], fun(vec![v(A)], html.clone())),
813 ),
814 (
815 "html_attr",
816 Prim::HtmlAttr,
817 poly(&[A], fun(vec![str_.clone(), v(A)], attr.clone())),
818 ),
819 (
820 "html_on",
821 Prim::HtmlOn,
822 poly(&[A], fun(vec![str_.clone(), v(A)], attr.clone())),
823 ),
824 (
825 "html_key",
826 Prim::HtmlKey,
827 poly(&[A], fun(vec![v(A)], attr.clone())),
828 ),
829 (
830 "uuid",
831 Prim::NewUuid,
832 Scheme::mono(fun_eff(vec![], str_.clone(), Row::of([Effect::Nondet]))),
833 ),
834 // The other half of §3.7's forbidden pair. `now()` is legal anywhere a clock exists and
835 // illegal inside a fold — which is a statement about its row, not about its name.
836 (
837 "now",
838 Prim::Now,
839 Scheme::mono(fun_eff(vec![], int.clone(), Row::of([Effect::Nondet]))),
840 ),
841 // §3.5's `type ApiKey = secret[str]`, given a source. Reading the process environment is
842 // `env`, which no client discharges — so a secret cannot even be *obtained* on the tier it
843 // must not reach, before Sendable is consulted at the boundary.
844 (
845 "secret_env",
846 Prim::SecretEnv,
847 Scheme::mono(fun_eff(
848 vec![str_.clone()],
849 Ty::secret(str_.clone()),
850 Row::of([Effect::Env]),
851 )),
852 ),
853 // §3.5's missing quadrant: storable, never Sendable.
854 //
855 // Wrapping is pure and free — recording a fact is not an effect. *Reading* one performs
856 // `cap.internal`, which no tier but the server discharges and which
857 // [`crate::secure`] discharges only inside the authority chokepoint. So a view cannot
858 // unwrap one to render it: not because rendering is forbidden, but because the view is not
859 // somewhere a capability is held.
860 (
861 "internal_of",
862 Prim::InternalOf,
863 poly(&[A], fun(vec![v(A)], Ty::internal(v(A)))),
864 ),
865 (
866 "reveal",
867 Prim::Reveal,
868 poly(
869 &[A],
870 fun_eff(
871 vec![Ty::internal(v(A))],
872 v(A),
873 Row::of([Effect::Cap(Arc::from("internal"))]),
874 ),
875 ),
876 ),
877 // ---- the signal vocabulary (§3.7) ----
878 //
879 // `merge_clients : () -> Stream[(Session × Command)] ! { ingress }`. Phase 1 has no tuple
880 // type, so the pair is the `Proposal` model the prelude declares below — the same shape,
881 // named.
882 (
883 "merge_clients",
884 Prim::MergeClients,
885 Scheme::mono(fun_eff(
886 vec![],
887 Ty::stream(Ty::con("Proposal")),
888 Row::of([Effect::Ingress]),
889 )),
890 ),
891 // `presence : () -> Signal[Map[Str, Int]] ! { cap.presence }` — D6's "who is connected
892 // now, as a first-class non-durable `Signal`".
893 //
894 // A map from actor to how many connections that actor has open, rather than a declared
895 // model: `corpus/15-presence.beck` had already written `here: Map[Str, Int]` by hand, the
896 // ordering is the key's and therefore a function of the value (`docs/54`), and every
897 // question a page asks of it — how many, who, is this one here — is a `Map` primitive that
898 // already exists.
899 (
900 "presence",
901 Prim::Presence,
902 Scheme::mono(fun_eff(
903 vec![],
904 Ty::signal(Ty::map(Ty::str_(), Ty::int())),
905 Row::of([Effect::Cap(Arc::from("presence"))]),
906 )),
907 ),
908 // `awareness : (Session -> T) -> Signal[Map[Str, T]]` — presence with a payload.
909 //
910 // Every subscriber contributes `f(session)` and reads everybody's, keyed by actor. The
911 // roster is what `presence()` gives with the payload it does not carry, and the shape is
912 // [Yjs's awareness protocol](https://docs.yjs.dev/getting-started/adding-awareness)
913 // deliberately: cursors, selections and typing indicators are ephemeral by nature and
914 // belong nowhere near the log.
915 //
916 // `f` reads the **`Session`** and nothing else, which is what makes this half buildable
917 // without a byte of new protocol — the server already holds every subscriber's route, and
918 // it arrives on `hello` and on every navigation. `docs/104` §104.8 is where the other half
919 // is specified, and what it waits on is a client-local value to publish rather than a way
920 // to publish one.
921 (
922 "awareness",
923 Prim::Awareness,
924 poly(
925 &[A],
926 fun_eff(
927 vec![fun(vec![Ty::con("Session")], v(A))],
928 Ty::signal(Ty::map(Ty::str_(), v(A))),
929 Row::of([Effect::Cap(Arc::from("presence"))]),
930 ),
931 ),
932 ),
933 // `freshness : () -> Signal[Freshness]` — §3.7's freshness dimension, as a source.
934 //
935 // The mirror of `presence` and deliberately not its shape: presence is a capability
936 // because a roster says something about other people, and a client's count of its own
937 // unacknowledged commands says something about nobody else. So the row is empty, and what
938 // constrains it is placement rather than authority — `crate::render` refuses a page that
939 // reads it and renders on the server, because a server holds no guesses and the answer
940 // there is `Confirmed` forever.
941 (
942 "freshness",
943 Prim::Freshness,
944 Scheme::mono(fun(vec![], Ty::signal(Ty::con("Freshness")))),
945 ),
946 // `gestures : ((S, G) -> S, S) -> Signal[S] ! { dom }` — D30's non-durable fold.
947 //
948 // It carries its step function for `awareness`'s reason: there is no stream to read. A
949 // gesture stream has exactly one consumer by construction — nothing else in the program
950 // could name it, because nothing else is on the client's side of the seam — so naming it
951 // would buy a declaration and no expressiveness.
952 //
953 // **The step takes the bare gesture, and that is the whole argument for a second
954 // primitive.** `fold`'s step takes an `Envelope[E]`, whose `seq` is §3.7's "position in the
955 // total order — assigned here, nowhere else". A gesture has no position in the total order:
956 // it is not in the order. Passing one an envelope would have to invent a `seq`, an `at` and
957 // an `actor` for something that was never recorded, which is a lie told in exactly the
958 // place D30 exists to be honest about. The signatures differ because the things differ —
959 // `(S, Envelope[E]) -> S` says this state is a function of the log, and `(S, G) -> S` says
960 // it is a function of nothing the log knows.
961 //
962 // `dom` is the placement and the semantics at once. The client is the only tier that
963 // discharges it ([`crate::ty::Tier::discharges`]), so this is client-placed without a rule
964 // being written for it, and `durable` — which only `data` and `server` discharge — is
965 // unreachable from where it lands. That is D30's "ephemerality comes from the stream"
966 // enforced as a type rather than promised as a convention.
967 (
968 "gestures",
969 Prim::Gestures,
970 poly_eff(
971 &[A, B],
972 &[E],
973 fun_eff(
974 vec![fun_eff(vec![v(A), v(B)], v(A), Row::var(E)), v(A)],
975 Ty::signal(v(A)),
976 Row::of([Effect::Dom]),
977 ),
978 ),
979 ),
980 (
981 "filter_map",
982 Prim::StreamFilterMap,
983 poly_eff(
984 &[A, B],
985 &[E],
986 fun_eff(
987 vec![
988 Ty::stream(v(A)),
989 fun_eff(vec![v(A)], Ty::option(v(B)), Row::var(E)),
990 ],
991 Ty::stream(v(B)),
992 Row::var(E),
993 ),
994 ),
995 ),
996 // §3.7: "`fold`'s function must be *replay-pure*: effect row ⊆ {}". That could be written
997 // as a closed empty row here, and unification would reject an impure fold — with a message
998 // about rows failing to unify. The row is a *variable* instead, so the row is inferred and
999 // then judged by `place`, which can say which effect, where it came from, and why the rule
1000 // exists. A checked property is worth no more than the diagnostic that delivers it.
1001 (
1002 "fold",
1003 Prim::Fold,
1004 poly_eff(
1005 &[A, B],
1006 &[E],
1007 fun(
1008 vec![
1009 fun_eff(
1010 vec![v(A), Ty::app(Ty::ENVELOPE, vec![v(B)])],
1011 v(A),
1012 Row::var(E),
1013 ),
1014 v(A),
1015 Ty::stream(v(B)),
1016 ],
1017 Ty::signal(v(A)),
1018 ),
1019 ),
1020 ),
1021 (
1022 "durable",
1023 Prim::Durable,
1024 poly(
1025 &[A],
1026 fun_eff(
1027 vec![Ty::signal(v(A))],
1028 Ty::signal(v(A)),
1029 Row::of([Effect::Durable]),
1030 ),
1031 ),
1032 ),
1033 // A signal edge carries its function's row to the signal, which is what makes a view that
1034 // reaches the log a *placement* error on the client rather than a runtime surprise.
1035 (
1036 "signal_map",
1037 Prim::SignalMap,
1038 poly_eff(
1039 &[A, B],
1040 &[E],
1041 fun_eff(
1042 vec![Ty::signal(v(A)), fun_eff(vec![v(A)], v(B), Row::var(E))],
1043 Ty::signal(v(B)),
1044 Row::var(E),
1045 ),
1046 ),
1047 ),
1048 (
1049 "map2",
1050 Prim::SignalMap2,
1051 poly_eff(
1052 &[A, B, C],
1053 &[E],
1054 fun_eff(
1055 vec![
1056 fun_eff(vec![v(A), v(B)], v(C), Row::var(E)),
1057 Ty::signal(v(A)),
1058 Ty::signal(v(B)),
1059 ],
1060 Ty::signal(v(C)),
1061 Row::var(E),
1062 ),
1063 ),
1064 ),
1065 (
1066 "per_session",
1067 Prim::PerSession,
1068 poly_eff(
1069 &[A, B],
1070 &[E],
1071 fun_eff(
1072 vec![
1073 Ty::signal(v(A)),
1074 fun_eff(vec![v(A), Ty::con("Session")], v(B), Row::var(E)),
1075 ],
1076 Ty::signal(v(B)),
1077 Row::var(E),
1078 ),
1079 ),
1080 ),
1081 // `validate : (Session, Command) -> list[Event]` (§3.7), with the accumulator threaded so
1082 // that client-minted ids can be checked for freshness and ownership against the actor —
1083 // the two obligations F2 puts on validation and the todo sketch deliberately skips.
1084 (
1085 "decide",
1086 Prim::Decide,
1087 poly_eff(
1088 &[A, B, C],
1089 &[E],
1090 fun_eff(
1091 vec![
1092 Ty::stream(Ty::con("Proposal")),
1093 Ty::signal(v(A)),
1094 fun_eff(
1095 vec![v(A), Ty::con("Proposal")],
1096 Ty::app(Ty::RESULT, vec![Ty::list(v(B)), v(C)]),
1097 Row::var(E),
1098 ),
1099 ],
1100 Ty::stream(v(B)),
1101 Row::var(E),
1102 ),
1103 ),
1104 ),
1105 ]
1106}
1107
1108/// Types every program has: `Option`, `Result`, `Envelope`, `Session`, `Proposal`.
1109///
1110/// `Envelope` is §3.7's, field for field — "`seq`: position in the total order — assigned here,
1111/// nowhere else; `at`: wall-clock, captured as data; `actor`: stable authenticated identity —
1112/// **never** the live `Session` capability or token".
1113pub fn types() -> BTreeMap<Arc<str>, TyDecl> {
1114 let mut out = BTreeMap::new();
1115 let mut add = |d: TyDecl| {
1116 out.insert(d.name().clone(), d);
1117 };
1118
1119 add(TyDecl::Union {
1120 name: Arc::from(Ty::OPTION),
1121 params: vec![Arc::from("T")],
1122 variants: vec![
1123 Variant {
1124 name: Arc::from("Some"),
1125 fields: vec![(Arc::from("value"), Ty::Var(A))],
1126 },
1127 Variant {
1128 name: Arc::from("None"),
1129 fields: vec![],
1130 },
1131 ],
1132 });
1133 add(TyDecl::Union {
1134 name: Arc::from(Ty::RESULT),
1135 params: vec![Arc::from("T"), Arc::from("E")],
1136 variants: vec![
1137 Variant {
1138 name: Arc::from("Ok"),
1139 fields: vec![(Arc::from("value"), Ty::Var(A))],
1140 },
1141 Variant {
1142 name: Arc::from("Err"),
1143 fields: vec![(Arc::from("error"), Ty::Var(B))],
1144 },
1145 ],
1146 });
1147 // JSON as data, so a program reads a document with `match` and builds one with ordinary
1148 // constructors. There is no reflection and no derive: `Json` is a union like any other, and
1149 // turning a `model` into one is a function somebody writes — which is what `@derive` is for
1150 // when it exists, and is not a reason to put a second kind of value in the language now.
1151 //
1152 // The variants are prefixed because a union's constructors are global names: `Str` and `Bool`
1153 // are taken, and `List` would be taken by anybody's own union the day they wrote one.
1154 add(TyDecl::Union {
1155 name: Arc::from("Json"),
1156 params: Vec::new(),
1157 variants: vec![
1158 Variant {
1159 name: Arc::from("JsonNull"),
1160 fields: vec![],
1161 },
1162 Variant {
1163 name: Arc::from("JsonBool"),
1164 fields: vec![(Arc::from("value"), Ty::bool_())],
1165 },
1166 // One number type, and it is the `Float` §32 built rather than a second numeric
1167 // tower: JSON's own grammar has one, and a reader who wants an integer asks for one.
1168 Variant {
1169 name: Arc::from("JsonNumber"),
1170 fields: vec![(Arc::from("value"), Ty::con(Ty::FLOAT))],
1171 },
1172 Variant {
1173 name: Arc::from("JsonStr"),
1174 fields: vec![(Arc::from("value"), Ty::str_())],
1175 },
1176 Variant {
1177 name: Arc::from("JsonList"),
1178 fields: vec![(Arc::from("items"), Ty::list(Ty::con("Json")))],
1179 },
1180 Variant {
1181 name: Arc::from("JsonObject"),
1182 fields: vec![(Arc::from("fields"), Ty::map(Ty::str_(), Ty::con("Json")))],
1183 },
1184 ],
1185 });
1186 // The error `json_parse` raises. A declared type rather than a `Str`, because
1187 // `docs/27` §27.7's atom names the type and a `raises(Str)` would make every string failure in
1188 // a program the same failure.
1189 add(TyDecl::Union {
1190 name: Arc::from("JsonError"),
1191 params: Vec::new(),
1192 variants: vec![Variant {
1193 name: Arc::from("BadJson"),
1194 fields: vec![(Arc::from("why"), Ty::str_())],
1195 }],
1196 });
1197 // The outbound call's three types. A request carries a port and *not* a host: the host is the
1198 // atom the call site performs, so it is an argument of `http_fetch` rather than a field
1199 // anything can compute.
1200 add(TyDecl::Model {
1201 name: Arc::from("HttpRequest"),
1202 params: Vec::new(),
1203 fields: vec![
1204 (Arc::from("method"), Ty::str_()),
1205 // Origin-form, sent as written: `/v1/todos?limit=10`. Nothing percent-encodes it,
1206 // because only the program that built it knows which part of it was data.
1207 (Arc::from("path"), Ty::str_()),
1208 // One value per name, which loses a repeated header (`Set-Cookie`). Said out loud
1209 // here rather than discovered: a `map` is what a program wants to read, and the day a
1210 // caller needs the repeats this becomes a `list[(Str, Str)]` and every reader changes.
1211 (Arc::from("headers"), Ty::map(Ty::str_(), Ty::str_())),
1212 (Arc::from("body"), Ty::str_()),
1213 (Arc::from("port"), Ty::int()),
1214 // Whether the exchange is inside a TLS session whose certificate names the host the
1215 // call site wrote. A field of the request rather than a mode of the client: a program
1216 // may reach one peer over a plaintext hop inside its own cluster and another across
1217 // the internet, and the two calls are two requests.
1218 (Arc::from("tls"), Ty::bool_()),
1219 // Headers whose *value* is a secret, kept apart from the ones whose value is a `Str`.
1220 //
1221 // §3.5 makes a `secret[T]` unreadable — there is no `reveal` for one, which is the
1222 // whole claim — so `"Bearer " + key` cannot be written and an authenticated request
1223 // would be inexpressible. These are merged into the headers by the runtime at the
1224 // edge, so the credential goes on the wire without ever having been a `Str` the
1225 // program could put somewhere else. A request carrying one is not Sendable, which is
1226 // the property doing the work: it cannot be built on, or sent to, a client.
1227 (
1228 Arc::from("secrets"),
1229 Ty::map(Ty::str_(), Ty::secret(Ty::str_())),
1230 ),
1231 ],
1232 });
1233 add(TyDecl::Model {
1234 name: Arc::from("HttpResponse"),
1235 params: Vec::new(),
1236 fields: vec![
1237 (Arc::from("status"), Ty::int()),
1238 (Arc::from("headers"), Ty::map(Ty::str_(), Ty::str_())),
1239 (Arc::from("body"), Ty::str_()),
1240 ],
1241 });
1242 // A status is a reply and not a failure — a 500 arrived, and a program that treats it as an
1243 // exception has lost the body that says why. These are the cases where *nothing* arrived,
1244 // plus the one the library raises when a caller asks for a status it did not get.
1245 add(TyDecl::Union {
1246 name: Arc::from("HttpError"),
1247 params: Vec::new(),
1248 variants: vec![
1249 Variant {
1250 name: Arc::from("HttpUnreachable"),
1251 fields: vec![
1252 (Arc::from("host"), Ty::str_()),
1253 (Arc::from("why"), Ty::str_()),
1254 ],
1255 },
1256 Variant {
1257 name: Arc::from("HttpTimedOut"),
1258 fields: vec![
1259 (Arc::from("host"), Ty::str_()),
1260 (Arc::from("millis"), Ty::int()),
1261 ],
1262 },
1263 Variant {
1264 name: Arc::from("HttpBadResponse"),
1265 fields: vec![(Arc::from("why"), Ty::str_())],
1266 },
1267 // Raised by `lib/http.beck`'s `require_ok`, never by the primitive.
1268 Variant {
1269 name: Arc::from("HttpStatus"),
1270 fields: vec![
1271 (Arc::from("status"), Ty::int()),
1272 (Arc::from("body"), Ty::str_()),
1273 ],
1274 },
1275 ],
1276 });
1277 add(TyDecl::Union {
1278 name: Arc::from("TimeError"),
1279 params: Vec::new(),
1280 variants: vec![Variant {
1281 name: Arc::from("BadTime"),
1282 fields: vec![(Arc::from("why"), Ty::str_())],
1283 }],
1284 });
1285 // The two the decoders raise. Separate types rather than one `BadInput`, because a caller
1286 // reading a base64 field and a caller reading an identifier are recovering from different
1287 // things: the first re-reads the message, the second rejects the request.
1288 add(TyDecl::Union {
1289 name: Arc::from("EncodingError"),
1290 params: Vec::new(),
1291 variants: vec![Variant {
1292 name: Arc::from("BadEncoding"),
1293 fields: vec![
1294 (Arc::from("encoding"), Ty::str_()),
1295 (Arc::from("why"), Ty::str_()),
1296 ],
1297 }],
1298 });
1299 add(TyDecl::Union {
1300 name: Arc::from("UuidError"),
1301 params: Vec::new(),
1302 variants: vec![Variant {
1303 name: Arc::from("BadUuid"),
1304 fields: vec![(Arc::from("why"), Ty::str_())],
1305 }],
1306 });
1307 add(TyDecl::Model {
1308 name: Arc::from(Ty::ENVELOPE),
1309 params: vec![Arc::from("T")],
1310 fields: vec![
1311 (Arc::from("seq"), Ty::int()),
1312 (Arc::from("at"), Ty::int()),
1313 (Arc::from("actor"), Ty::str_()),
1314 (Arc::from("body"), Ty::Var(A)),
1315 ],
1316 });
1317 // "`Session` is minted by the identity subsystem … with verified claims mapped to typed
1318 // capabilities" (§3.7).
1319 //
1320 // `claims` is the second half, and it is a `map[Str, Str]` rather than a type per issuer: what
1321 // an issuer emits is that issuer's decision, so a program that reads `session.claims` is
1322 // reading somebody else's vocabulary and the type says so. It is **empty** under a provider
1323 // that verifies nothing, which is what makes `map_get(session.claims, "role")` a check rather
1324 // than a decoration.
1325 //
1326 // It does not reach the log: an `Envelope` carries `actor` and nothing else, because a fold
1327 // whose replay depended on what the issuer was saying at the time would not be a fold
1328 // (`docs/48` §48.6).
1329 //
1330 // `path` is the third thing a page may be a function of, and it is a different *kind* of thing
1331 // from the other two: `actor` and `claims` say who is asking and are what a provider verified,
1332 // `path` says where they are and is the client's own statement about itself. Nothing verifies
1333 // it and nothing should — a route is not evidence. It is here rather than on a type of its own
1334 // because a view is handed one record at the edge and the route arrives on the same connection
1335 // as the identity does; [`crate::render`] is where the difference between the two halves is
1336 // made structural, since a Mode B page may read where it is and may not read who is asking.
1337 //
1338 // The URL's *path* and nothing else. A fragment never reaches a server, so carrying one would
1339 // be a field whose value differs between the two rendering modes; a query string is a second
1340 // vocabulary with its own parsing, and path segments are available today.
1341 add(TyDecl::Model {
1342 name: Arc::from("Session"),
1343 params: Vec::new(),
1344 fields: vec![
1345 (Arc::from("actor"), Ty::str_()),
1346 (Arc::from("claims"), Ty::map(Ty::str_(), Ty::str_())),
1347 (Arc::from("path"), Ty::str_()),
1348 ],
1349 });
1350 // §3.7's freshness dimension, written out: "`Signal[T]` carries a freshness dimension
1351 // (`confirmed | pending(n)`) that UI code can render (\"saving…\") — staleness is typed, not
1352 // pretended away."
1353 //
1354 // Two variants rather than a count, because `Pending(n=0)` and `Confirmed` would be one fact
1355 // with two spellings and a page would have to know which one it was handed. `n` is on the
1356 // pending variant for the same reason a `Some` carries its value: it only exists when there is
1357 // something to count.
1358 add(TyDecl::Union {
1359 name: Arc::from("Freshness"),
1360 params: Vec::new(),
1361 variants: vec![
1362 Variant {
1363 name: Arc::from("Confirmed"),
1364 fields: vec![],
1365 },
1366 Variant {
1367 name: Arc::from("Pending"),
1368 fields: vec![(Arc::from("n"), Ty::int())],
1369 },
1370 ],
1371 });
1372 add(TyDecl::Model {
1373 name: Arc::from("Proposal"),
1374 params: Vec::new(),
1375 fields: vec![
1376 (Arc::from("session"), Ty::con("Session")),
1377 (Arc::from("command"), Ty::con("Command")),
1378 ],
1379 });
1380 out
1381}
1382
1383/// The traits every program has.
1384///
1385/// One, and it is the one SICP §2.5.1 builds by hand: **generic arithmetic**. The book's answer to
1386/// "how do rationals join a tower that already has integers" is a set of generic operations —
1387/// `add`, `sub`, `mul`, `div` — that each type installs an implementation for, and that is exactly
1388/// a trait. `docs/27` §27.2 resolved `+` from its operands and said an ad-hoc resolution was "the
1389/// honest thing to build before traits exist"; they exist, so `+` resolves through this when its
1390/// operands are neither `Int` nor `Float` nor `Str`.
1391///
1392/// The method names are the book's. A tower is only worth having if a third floor can be added
1393/// from outside the language, and `impl Num for Rational` is how §2.1.1's exercise stops being
1394/// about function names and starts being about data abstraction.
1395///
1396/// It is **not published**: `own_traits` is what a `.becki` carries, and this belongs to the
1397/// language rather than to any module. Nor is it implemented for `Int` or `Float` — those go
1398/// through the primitives, because a tower whose bottom floor is a dictionary call would make every
1399/// existing program slower to prove a point.
1400pub fn traits() -> Vec<TraitSig> {
1401 let binary = |name: &str| MethodSig {
1402 name: Arc::from(name),
1403 params: vec![
1404 (Arc::from("self"), Ty::con(SELF)),
1405 (Arc::from("other"), Ty::con(SELF)),
1406 ],
1407 ret: Ty::con(SELF),
1408 effects: Vec::new(),
1409 };
1410 vec![TraitSig {
1411 name: Arc::from(NUM),
1412 methods: vec![binary("add"), binary("sub"), binary("mul"), binary("div")],
1413 }]
1414}
1415
1416/// The trait `+`, `-`, `*` and `/` resolve through.
1417pub const NUM: &str = "Num";
1418
1419/// The abstract receiver a trait's signatures are written in terms of.
1420const SELF: &str = "Self";
1421
1422/// Which method of [`NUM`] an operator is.
1423pub fn num_method(op: Prim) -> Option<&'static str> {
1424 Some(match op {
1425 Prim::Add => "add",
1426 Prim::Sub => "sub",
1427 Prim::Mul => "mul",
1428 Prim::Div => "div",
1429 _ => return None,
1430 })
1431}
1432
1433/// The type-constructor arities the checker knows without a declaration.
1434pub fn builtin_arity(name: &str) -> Option<usize> {
1435 Some(match name {
1436 Ty::INT | Ty::STR | Ty::BOOL | Ty::FLOAT | Ty::UNIT | Ty::HTML | Ty::ATTR => 0,
1437 Ty::LIST
1438 | Ty::OPTION
1439 | Ty::STREAM
1440 | Ty::SIGNAL
1441 | Ty::ENVELOPE
1442 | Ty::SECRET
1443 | Ty::INTERNAL => 1,
1444 Ty::MAP | Ty::RESULT => 2,
1445 _ => return None,
1446 })
1447}
1448
1449#[cfg(test)]
1450mod tests {
1451 use super::*;
1452
1453 #[test]
1454 fn every_prim_has_a_signature_and_the_names_are_unique() {
1455 let all = prims();
1456 let mut names: Vec<&str> = all.iter().map(|(n, _, _)| *n).collect();
1457 names.sort_unstable();
1458 let before = names.len();
1459 names.dedup();
1460 assert_eq!(before, names.len(), "duplicate prelude name");
1461 for (name, prim, _) in &all {
1462 assert_eq!(*name, prim.name(), "prelude name must match Prim::name");
1463 }
1464 }
1465
1466 #[test]
1467 fn folds_type_the_way_section_3_7_says() {
1468 // `fold(f, init, s) : Signal[S]` where `f : (S, Envelope[E]) -> S`.
1469 let all = prims();
1470 let (_, _, scheme) = all
1471 .iter()
1472 .find(|(n, _, _)| *n == "fold")
1473 .expect("fold exists");
1474 match &scheme.ty {
1475 Ty::Fun(params, ret, _) => {
1476 assert_eq!(params.len(), 3);
1477 assert_eq!(ret.con_name(), Some(Ty::SIGNAL));
1478 assert!(matches!(¶ms[0], Ty::Fun(ps, _, _) if ps.len() == 2));
1479 assert_eq!(params[2].con_name(), Some(Ty::STREAM));
1480 }
1481 other => panic!("fold should be a function, got {other}"),
1482 }
1483 }
1484
1485 #[test]
1486 fn the_standard_library_is_effect_polymorphic_where_section_3_2_says_it_is() {
1487 // "Effect polymorphism is what keeps one standard library." If `map_list` were monomorphic
1488 // in its function's row there would have to be a pure `map` and an effectful `map`, and the
1489 // choice would be the caller's problem rather than the compiler's.
1490 let all = prims();
1491 for name in [
1492 "map_list",
1493 "filter_list",
1494 "sort_by",
1495 "signal_map",
1496 "per_session",
1497 "decide",
1498 ] {
1499 let (_, _, scheme) = all
1500 .iter()
1501 .find(|(n, _, _)| *n == name)
1502 .unwrap_or_else(|| panic!("{name} exists"));
1503 assert!(
1504 !scheme.row_vars.is_empty(),
1505 "`{name}` takes a function, so it must be polymorphic in that function's row"
1506 );
1507 }
1508 // …and the effectful primitives carry their atom, closed.
1509 for (name, atom) in [
1510 ("merge_clients", Effect::Ingress),
1511 ("durable", Effect::Durable),
1512 ("uuid", Effect::Nondet),
1513 ("now", Effect::Nondet),
1514 ("secret_env", Effect::Env),
1515 ] {
1516 let (_, _, scheme) = all.iter().find(|(n, _, _)| *n == name).unwrap();
1517 let Ty::Fun(_, _, row) = &scheme.ty else {
1518 panic!("{name} is a function")
1519 };
1520 assert!(
1521 row.atoms.contains(&atom),
1522 "`{name}` should perform `{atom}`"
1523 );
1524 }
1525 }
1526
1527 #[test]
1528 fn every_primitives_own_atoms_agree_with_its_scheme() {
1529 // Two statements of the same fact — the table `Prim::effects` returns and the row in the
1530 // scheme — so a primitive cannot acquire an effect in one and not the other.
1531 for (name, prim, scheme) in prims() {
1532 let Ty::Fun(_, _, row) = &scheme.ty else {
1533 continue;
1534 };
1535 for e in &prim.effects() {
1536 assert!(
1537 row.atoms.contains(e),
1538 "`{name}` performs `{e}` but its scheme does not say so"
1539 );
1540 }
1541 for e in &row.atoms {
1542 assert!(
1543 prim.effects().contains(e),
1544 "`{name}`'s scheme carries `{e}` but `Prim::effects` does not"
1545 );
1546 }
1547 }
1548 }
1549
1550 #[test]
1551 fn the_envelope_carries_an_actor_and_never_a_session() {
1552 let ts = types();
1553 match ts.get(Ty::ENVELOPE).expect("Envelope exists") {
1554 TyDecl::Model { fields, .. } => {
1555 let names: Vec<&str> = fields.iter().map(|(n, _)| n.as_ref()).collect();
1556 assert_eq!(names, ["seq", "at", "actor", "body"]);
1557 assert!(!names.contains(&"session"), "F5: no capability in the log");
1558 }
1559 other => panic!("Envelope should be a model, got {other:?}"),
1560 }
1561 }
1562}