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!(&params[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}