# DKE Python — Language Reference **Version:** 13.0.0, changed in rev 148 · alpha · API 1.0 · rev 148\ **Status:** Published reference for DKE Python.\ **Audience:** Programmers writing DKE Python — the programmer's day-to-day lookup for verbs, types, statements, operators, classes, and modules.\ **This document as Markdown:** [dke-python-ref.md](/ref/dke/python/dke-python-ref.md) — the same text this page is rendered from, for readers and tools that would rather have the source than the page. --- ## 1. About this reference This is the **reference manual** for **DKE Python**, the language a DKE customer writes and submits to the DKE service at `dke.langsyn.net`. It is the published description of the language: what each construct means, what it produces, and how to write it — one entry at a time, with a worked example for each. Read it alongside its two companions: - the [**Python Tutorial**](/tutorial/dke/python/), which teaches the same language in task order, from a first program to classes, provenance and modules; - the [**MCP Reference**](/ref/dke/mcp/), which specifies the transport that carries a program to the service — the tool surface, request shapes, the response envelope and the error model. If you come to DKE Python with a distinction in mind (unknown against false, disagreement against uncertainty, a hypothesis against a fact), start at §15, which names the verbs behind each. Where this Reference and the running service appear to differ, tell us: one of the two is wrong, and it matters which. The language and this document are open under the MIT licence. The DKE service that runs DKE Python is LangSyn's proprietary product — the language is published; the implementation is not. DKE Python is a **Python-flavoured surface** over DKE's knowledge-operation semantics: `def` declarations, bare assignment, `match`/`case`, `try`/`except … as`, and an explicit `self` receiver. It is **indentation-significant**. Source files use the extension `.dpy`. Its standing rules (§8.8) are Datalog-class — the terminating fragment of logic programming — because a service that runs rules its customers submit has to promise that every one of them terminates. --- ## 2. A program at a glance A DKE Python source file is a **module**: one or more `def` declarations. Every declaration is stored under its own name and is independently invocable. ``` # greet.dpy — a one-declaration module. def greet(K: string, s: string): remember(K.s.mood, "cheerful", "op1") # write a claim m = current(K.s.mood) # read it back (bare assignment) match m: # consume the discriminator case active_claim: print("mood = " + str(m.value)) case empty: print("no mood recorded") ``` Reading the shape: - `def greet(K: string, s: string):` — a **declaration** (§7.1): the `def` keyword, name, typed parameters (`name: type`), a `:` and an indented suite. - `remember(…)`, `current(…)` — **verb calls** (§5): the built-in verb surface written as function calls with a dotted `K.s.a` slot path as the first argument. - `match` / `case` — a **statement** (§7.8): exhaustive discriminator dispatch. - `m.value` — **field access** (§11) on an `active_claim`. Compile it as the module `greet`, then run it at the wire surface with `run(source)`, where the source imports the module and calls the script: `import greet`, then `greet.greet("Cat", "felix")` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). In another program, call it as any function: `greet(K, s)`. **Learn from inside the language.** Send a unit whose body is `--help` for orientation, or `--help ` for one of `header`, `declarations`, `statements`, `expressions`, `types`, `verbs`, `scripts`. Classes, rules and constraints are under `declarations`. ``` --help verbs ``` --- ## 3. Natural language The natural language a source is written in — which keyword vocabulary its statements use, and the language its responses render in — is set by the `language` parameter supplied when the source is submitted. - When the `language` parameter is omitted, the language is the one the source declares (below), and when it declares none, **English** (`eng`), which is the language this Reference describes. The parameter's accepted values belong to the wire contract and are listed in [MCP Reference §8.1](/ref/dke/mcp/#what-language-selects); a value outside that set is declined before the source is compiled. - **This Reference describes the English surface.** Every keyword, verb, operator spelling and rendered response shown here is the `eng` one. - The natural language is a property of how you talk to the service, set per call, and the call decides it. - **A source may say what it is written in**, on its first line, so that a file read later carries it: ```python # dke: edition=python.1 language=eng.1 print("written in English, at API 1") ``` The line is a comment, and optional. `python.1` is DKE Python at API MAJOR 1, API 1.0 and every later 1.N, and `eng.1` is version 1 of the English vocabulary, which moves only when a word is removed or respelled. A declared name supplies the parameter a call leaves out; a parameter naming another value is refused; a version, where one is written, must be the one the service serves, and a version left out is that one. A file meant to be compiled later should write both versions. The form and the rules are in the [Python Specification §3.9.1](/spec/dke/python/#the-first-line-declaration). --- ## 4. Types DKE Python has a **closed** vocabulary of built-in types, and one way to add to it: a class you declare is a type (§4.1, §8), and a subclass is accepted wherever its base is named (§8.10). There are no user-defined generics and no record types beyond classes. Programs compose by operating on the store through verbs. ### 4.1 The base types | Type | Produced by | Category | Notes | |------|-------------|----------|-------| | `string` | literals, user data, values | primitive | no ordering; `==` / `!=` only | | `int` | literals, `.length`, counts | primitive | signed 64-bit | | `bool` | comparison + logical results, `True` / `False` | primitive | | | `value` | `active_claim.value` (a claim's stored content) | tagged union | `int` \| `bool` \| `string` \| `datetime` \| `duration` \| `real` \| `null` \| `blob` \| `scientific` \| `complex`; consume with `match` (§7.8) | | `datetime` | `datetime("…Z")` literal, a datetime `value` read | value class | a UTC instant; renders as RFC 3339; a write-verb value + `case datetime as d:` | | `duration` | `duration("…")` literal, a duration `value` read | value class | a length of time; renders as ISO 8601 (`PnDTnHnMnS`); a write-verb value + `case duration as u:` | | `real` | `3.5` literal, a real `value` read | value class | a fractional number, written `3.5` or with an exponent `1e5` ([Python Specification §3.5.7](/spec/dke/python/#real-literals)); renders exactly as CPython `repr(float)` (fixed, or scientific for extreme magnitudes); computes under `+ - * / // % **` (§6.3) + ordered comparison (§6.1); a write-verb value + `case real as r:` | | `blob` | `blob("…")` / `blob_b64("…")` literal, a blob `value` read | value class | an opaque byte string, written `blob("…")` or `blob_b64("…")` ([Python Specification §3.5.8](/spec/dke/python/#blob-literals)); renders as canonical uppercase hex; a write-verb value + `case blob as b:` | | `scientific` | `scientific("…")` literal, a scientific `value` read | value class | a number carrying its significant figures, written `scientific("6.022e23")` ([Python Specification §3.5.14](/spec/dke/python/#scientific-literals)); reads back canonical (`60.220e21` → `6.0220e22`), with 4-s.f. and 5-s.f. of the same magnitude distinct; a write-verb value + `case scientific as s:` | | `complex` | `4j` literal, `3 + 4j`, a complex `value` read | value class | a number with a real part and an imaginary part ([Python Specification §3.5.15](/spec/dke/python/#complex-literals)); renders as CPython `repr(complex)` (`(3+4j)`, `4j`); computes under `+ - * / **` (§6.3), compares with `==` / `!=` and has no order (§6.1); a write-verb value + `case complex as z:` | | `slice` | `slice(stop)`, `slice(start, stop[, step])` | sequence selector | three bounds, each an `int` or open (`None`); `xs[s]` selects as `xs[a:b:c]` does; `.start` / `.stop` / `.step` and `.indices(n)`; renders as CPython `repr(slice)` (`slice(1, 5, None)`); `==` / `!=`, no order, not hashable, not a write-verb value (§6.5) | | `active_claim` | `current(K.s.a)`, a field read | discriminator | one live claim, or `empty` | | `claim_list` | `get(K.s.a)`, `list_values(K.a)`, `caveats(K.s)`, `dependents(h)`, `premises(h)`, `conflicts()` | finite collection | element `active_claim` | | `proof_tree` | `why(K.s.a)`, `why(K.s.a, datetime("…"))` | finite collection | element `active_claim` (the derivation's participating claims); also fields `.root`, `.truncated` | | `diagnosis` | `why_not(K.s.a)`, `what_needs(K.s.a)` | finite collection | element `string` (the lines of the explanation) | | `subject_set` | `subjects(K.a, v)`, `list_subjects(K)`, `list_subjects(K.a)` | finite collection | element `string` | | `string_list` | `list_categories()`, `list_attributes(K)`, `list_pinned()`, `list_scripts()` | finite collection | element `string` | | `verify_result` | `verify(K.s.a, v)` | discriminator | fields `.match`, `.actual`, `.expected`, `.conflicted` | | `result_type_set` | `list_result_types()` | finite collection | element `string` | | `refuse_info` | the binding of `except refuse as e` (§10) | handler binding | fields `.reason`, `.teaching_hint`, `.discriminator` (§11.2) | | `engine_error_info` | the binding of `except engine_error as e` (§10) | handler binding | fields `.reason`, `.code` (§11.2) | | `empty` | the second arm of an `active_claim` match | discriminator sentinel | `case empty:` only, not a type name | Beyond the base types there are four **constructed** type formers — values a program builds, which exist only while it runs and are never stored: `list` (an ordered sequence, §6.7), `tuple` (a fixed-length heterogeneous sequence, §6.8), `dict` (a mapping from a hashable key — a scalar or a tuple of hashables — to a value, kept in insertion order, §6.9), and `set` (a collection of unique hashable elements — a scalar or a tuple of hashables — presented sorted, §6.10). A `list` is produced by a list literal, a comprehension, `s.split(...)`, and a list slice; the other three by their own literals (§6.8–§6.10) and comprehensions (§6.11) — none by a read verb. Each shares the read surface it can support (index, `.length`, `for … in`, membership) with per-type refinements, and all render in `print` / `+`. None is one of the base types — they are type formers over them. A fifth, `iterator`, is what `zip`, `enumerate`, `reversed` and `iter` return: a one-pass reader over a collection, with no length and no position (§6.12). Three more, `dict_keys`, `dict_values` and `dict_items`, are the views a dict's `.keys()`, `.values()` and `.items()` return, which read the dict as it stands whenever they are read (§6.9). This table is the type vocabulary. `empty` has a row because you write it in a `match` arm, and it is not one of the types: it names the absent arm of an `active_claim`, and the compiler refuses it as an annotation in exactly those words. **Listing order is part of the contract.** Each finite collection has a defined order, and `claim_list` has two — a read within one cell and a read spanning several answer different questions: | type | order | |------|-------| | `subject_set` | ascending name order | | `string_list` | ascending name order | | `claim_list` spanning more than one cell — `list_values`, `conflicts`, `caveats`, `dependents`, `premises` | ascending name order | | `claim_list` from one cell — `get(K.s.a)` | the claim **in force** first, then the superseded ones oldest-first | | `proof_tree` | structural: the conclusion, then the claims it rests on | | `diagnosis` | structural: the explanation's own line order | | `result_type_set` | ascending name order (canonical name, §5.5) | **Ascending name order** is the order `sorted()` gives — codepoint order, not locale collation — the same sorted order a constructed `set` is presented in. So `xs == sorted(list(xs))` holds for every row that says ascending, and sorting such a read again is dead code. This fixes what the order **is**, not only that it does not change between runs: the two structural rows bind just as tightly, so a `proof_tree` always leads with its conclusion and a `diagnosis` always reads top to bottom as written. Three groupings drive the static rules: - **Primitives** — `string`, `int`, `bool`. - **Finite collections** — `claim_list`, `string_list`, `subject_set`, `result_type_set`, `proof_tree` (all store-derived), and `diagnosis`, the explanation `why_not` and `what_needs` give of an absence, plus the client-built `list`. These, together with a constructed `dict` (walked over its keys) and `set` (walked in sorted order), are the types a `for … in` may iterate (§7.7); every one also has a `.length` (§11). `claim_list` and `proof_tree` yield `active_claim` elements; the other three store-derived collections and `diagnosis` yield `string`; a `list` yields `T`. A `proof_tree` additionally offers a `.root` accessor (§11.2). (A `tuple` joins them when its elements have one type to bind — always for a variadic tuple, and for a fixed one whose positions share a type, are all scalars or are all class instances; a fixed tuple holding a collection beside a value of another type is **not** a `for … in` iterand, §6.8.) - **Discriminators** — `active_claim` (+ its `empty` case) and `verify_result`. Consumed by a `match`, alongside the `value` union. - **The `value` union** — the stored content of a claim (`active_claim.value`), one of these classes: `int`, `bool`, `string`, `datetime`, `duration`, `real`, `null`, `blob`, `scientific` or `complex`. All but one are **write-producible** — `int`, `bool`, `string`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex` (a program writes one and reads it back at that class). The remaining class, `null` (an explicit absent value), is **read-only**: it is asserted via `assert` (§5.1), no value literal produces it, but a claim carrying one is discriminable on read. Consume the union with a value `match` (§7.8) or an `isinstance` test (§8.11) to recover the typed content, or render it directly. - **`datetime`** — a UTC instant written with the `datetime("…Z")` literal (e.g. `datetime("2026-01-02T03:04:05Z")`, RFC 3339 with a required `Z` and optional 1–9 fractional-second digits). A write verb stores it typed and it reads back as the `datetime` class of `value`; it renders as its RFC 3339 text. - **`duration`** — a length of time written with the `duration("…")` literal (e.g. `duration("PT1H30M")`, an ISO 8601 duration `PnDTnHnMnS`; calendar years and months are not accepted). The lowest-order term may carry a decimal fraction of up to nine digits, so durations are exact to the nanosecond (`duration("PT0.5S")`); only the last term may be fractional, and a span must fit within about ±292 years. A write verb stores it typed and it reads back as the `duration` class of `value`; it renders as ISO 8601 in a canonical form with trailing fractional zeros removed, so `duration("PT90M")` reads back as `PT1H30M` and `duration("PT1.500S")` as `PT1.5S`. - **`real`** — a fractional number written as a decimal literal `3.5`, a **leading-dot** `.5` (== `0.5`), or a **trailing-dot** `5.` (== `5.0`); `_` may separate digits as in `1_000.5`. A lone `.` with no adjacent digit is the field operator, so `.name` is a field access, and `5.name` is the real `5.0` then `name` (a parse error), not a field on an integer. A literal may also carry an **exponent** `1e5`, `1.5e-3`, `2E10`, `.5e2` (`e`/`E`, optional sign, digits — a real even without a point; `1e` with no digit stays an integer `1`). A write verb stores it typed and it reads back as the `real` class of `value`. It renders exactly as CPython `repr(float)`: shortest round-trip, fixed-point for a decimal exponent in `[-4, 15]` (trailing zeros dropped — `3.50` → `3.5`, `5.0` → `5.0`), scientific (`1e+20`, `1.5e-08`) beyond that, `-0.0` for negative zero, and `inf`, `-inf` and `nan` for the two infinities and a NaN — the same text on the wire, on write-through, and on read-back: a cell holds every real exactly, `-0.0`, the infinities and a NaN included. An exponent past the top of the finite range is the infinity, `1e400` being `inf`, as in Python; one past the bottom (`1e-400`) underflows to `0.0`. There is no NaN literal: `math.nan` and `float("nan")` make one, and `math.inf` an infinity (§6.14). A result past the largest `real` is `inf` or `-inf`, and an operation with an infinite or NaN operand answers IEEE 754's value (§6.3, *Infinity and NaN*). A `case real as r:` arm binds `r : real`. Reals **compute**: `+ - * /` and the ordered comparisons accept them and promote `int → real`; `/` is always true division and always yields a `real` (§6.3). - **`blob`** — an opaque byte string written as a hex literal `blob("48656C6C6F")` (an even count of hex digits) or a base64 literal `blob_b64("SGVsbG8=")` (RFC 4648; the same bytes) — two input spellings for one byte space; the empty `blob("")` / `blob_b64("")` is a valid zero-byte blob. A write verb stores it typed and it reads back as the `blob` class of `value` in a single canonical uppercase-hex form regardless of the input spelling (a value written `blob_b64("SGVsbG8=")` reads back `48656C6C6F`); it renders as that hex text whichever spelling produced it, and the two spellings and a read of the same bytes are one value, equal by their bytes wherever blobs compare (`blob_b64("AQI=") == blob("0102")` is `True`, and a set or dict holds them as one element or key). A blob indexes, slices and joins as Python's `bytes` do (§6.5). A `case blob as b:` arm binds `b : blob`. There is no bytes literal: `b"abc"` is refused naming the blob it denotes, `blob("616263")` ([Python Specification §3.5.8](/spec/dke/python/#blob-literals)). - **`scientific`** — a number carrying its significant figures, written with the `scientific("…")` factory (e.g. `scientific("6.022e23")`) — a mantissa and a **required** exponent. A write verb stores it typed and it reads back as the `scientific` class of `value`, in a canonical form with the mantissa normalized to one leading digit and the significant figures preserved, so `scientific("60.220e21")` reads back `6.0220e22`. The figures are part of the value: `scientific("6.022e23")` (four figures) and `scientific("6.0220e23")` (five) are the same magnitude but distinct values — the property a `real` cannot hold. A `case scientific as s:` arm binds `s : scientific`. - **`complex`** — a number with a real part and an imaginary part. An imaginary literal is a decimal number followed by `j` (`4j`, `1.5j`, `1e3j`), and a complex with a real part is written as the sum it is, `3 + 4j`, as in Python. It renders as CPython does: `4j` when the real part is a positive zero, otherwise both parts in parentheses, `(3+4j)`. `.real` and `.imag` read the two parts, each a `real`, and `z.conjugate()` is the conjugate. It computes under `+ - * /` beside a complex or a number (§6.3), compares with `==` and `!=` (`1 + 0j == 1` is `True`), and has **no order**: `<` and its kin refuse one, as in Python, and so do `sorted`, `min` and `max`. A write verb stores it typed and it reads back as the `complex` class of `value`; a cell holds zero without a sign, so `-0j` reads back `0j`. A `case complex as z:` arm binds `z : complex`. A declared `complex` takes a number too and keeps its class (`z: complex = 1` prints `1`), and so does a collection: `[1, 2j]` is a `list` whose elements keep their classes, so `sum([1, 2j])` is `(1+2j)`, and it still refuses `sorted`. Every number answers `.real`, `.imag` and `.conjugate()` too, as in Python (§11.2): it is its own real part and its own conjugate, a `bool` the int it is, and its imaginary part is a zero of its class. A `real` also answers `.is_integer()` and `.hex()`; an `int` or a `bool` answers `.bit_length()` and `.bit_count()` and has `.numerator` and `.denominator`; and each answers `.as_integer_ratio()`, which refuses a ratio past the signed 64-bit range. There is **no `None`**: every name holds one type, and no type means "a value or nothing". To assert that a stored value is known to be absent, use `assert(K.s.a, src)` (§5.1), which carries no value; a parameter that may be omitted takes a default of its own type; a script with no result ends with a bare `return`. `null` is written only as an open bound of a validity window and as the `case null:` arm that matches one, and `None` only as a whole argument of `slice(...)`, where it leaves that bound open (§6.5). Both words are reserved: neither can be bound as a name, and reading either where a value goes is refused with this lesson. A type is written in four places — a parameter, a binding annotation, a collection's element type, and a script's or value method's return — and the same names are valid in all four. They are any of the **value-class scalars** (`string`, `int`, `bool`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex`), a read-result type (`claim_list`, `active_claim`, …, for calls between procedures), a **collection type** (`list`, `dict`, …), or a **declared class** (§8), which stands for an instance of that class. CPython's spellings are read too: `str` is `string`, `float` is `real`, `list[int]` is `list` and `dict[str, int]` is `dict`. A return annotation of `None` alone, `-> None:`, says what no annotation says. A class name is a type wherever any other is, so `list` names a list of animals and `-> Animal` a script that answers one; which instances fit such a name is §8. A value-class-scalar parameter supplied over the wire is passed as its canonical text (a `datetime` as RFC 3339 UTC, a `duration` as ISO 8601, a `real` as a decimal with a point, a `blob` as hex) and refuses if malformed. A **class field** (§8) is typed by a value-class scalar only — a field holds one stored value. Neither `empty` nor `null` is a type — both name absence; `value` arises only from `.value` field access. ### 4.2 Handler bindings The `except refuse as ` / `except engine_error as ` handlers of a `try` (§7.9) each bind a value whose fields are all `string` and always present — a field with nothing to say holds `""`, since the language has no absent value. A refuse binds `.reason`, `.teaching_hint`, and `.discriminator`; an engine error binds `.reason` and `.code`. The fields are listed in §11.2. These types cannot be written as parameter types. `.reason` is written for a reader and is what a program should report. The `.discriminator` of a refuse and the `.code` of an engine error play the same role for a program that must **branch** rather than report: each is a short tag naming which failure this is. Two kinds of `.discriminator` are part of this contract. A refusal you raised yourself with `raise (…)` (§7.15) carries the exception name your own source wrote. A refusal the store raises because a call cannot succeed as written carries the code the [**MCP Reference**](/ref/dke/mcp/) publishes for it as `refuse_reason`, such as `conflicting_write`, `pinned_address` or `invalid_value`, and a program may branch on those codes as a client of the service does. Every other tag is not part of this contract and is not enumerated here — branch on the tags you have observed and keep a fallback arm. `.teaching_hint` suggests how to correct the call, and is empty when there is no suggestion. It is an aid to a human reader — never a second reason, and never something to branch on. --- ## 5. Verbs The built-in verb surface is written as **function calls** — a verb name and a parenthesized, comma-separated argument list: `remember(K.s.a, v, src)`, `current(K.s.a)`, `list_attributes(K)`. The first argument is the dotted `K.s.a` slot path (or a name literal or a claim, per the verb). Read verbs bind by **assignment** (`cur = current(K.s.a)`). Path slots: `K` (kind), `K.s` (kind + subject), `K.s.a` (full cell), `K.a` (kind + attribute). Each segment is either a **bare name** written literally — `remember(Greeting.hello.text, …)`, which is what the examples throughout use — or a `string`-typed parameter or binding in scope, which is how a script is parameterized over the slot it writes. The two forms compile to the same path; a bare name is not a variable reference and needs nothing declared. A `K.s.a` cell may also be written as an instance's field, `s.temp`, for every verb that takes one, and a `K.s` subject as the instance itself, `s`, for `caveats` and `list_attributes` (§8.3). The call form's compound heads (`list_attributes`, `list_values`, `list_subjects`, `list_categories`, `list_result_types`, `list_pinned`, `list_scripts`, `create_world`, `merge_world`, `discard_world`, `list_worlds`, `compare_worlds`) are single snake_case tokens, and like every verb's word they are **soft keywords** recognized as a verb only immediately before `(`, so the same spelling stays a usable ordinary identifier elsewhere. A verb takes only the keyword arguments its section names, such as `known_at=` on a read. A keyword it does not take is refused naming the ones it does, or saying it takes its arguments by position. Its keyword arguments come after its positional ones, as in any Python call. One it takes, written before them, is refused saying they come first, and a call writes one: a second, after another, is refused saying so. It takes as many positional arguments as its section writes, and one more is refused at that argument, saying how many the verb takes. So are one more to a fold (§5.8), to `len`, `isinstance` or `issubclass`, and to a literal factory such as `datetime("…")`. Where its section writes a path, the path is the whole argument, and an argument that goes on past it as an expression, as `current(K.s.a + 1)` does, is refused at the argument, saying so. So is an argument there that is no path, as in `current("s")`, saying what a path there wants. A call short of an argument it takes, as `remember(K.s.a)` is, is refused at its `)`, saying how many it takes at least. So is one to a hypothesis, a fold (§5.8), `len`, `sorted`, `isinstance` or `issubclass`. ### 5.1 Write verbs (statement position) #### `remember(K.s.a, v, src)` Records value `v` at cell `K.s.a` attributed to `src`. Permissive. The value is a scalar literal (`string` / `int` / `bool`) and is stored **with its type** — the claim reads back through `.value` at the type it was written (§4.1, `value`), and query verbs match it type-aware (`subjects`, `verify`, `agreement`). An optional pair of trailing `datetime` bounds gives a **validity window** — the fact is valid only between them (both inclusive); either bound may be `null` for an open side. Omit the pair for an always-valid fact. A windowed write is selected by an as-of read (§5.4) whose time falls in the window — that is how it is read back, and on the traversal verbs how it is found at all. The same source restating the same value over a **different** window that overlaps the one it gave is refused as a conflicting write — use `update` to move the window. A window that does not overlap it is a second claim, for another period. ``` remember(K.s.color, "orange", "op1") # string remember(K.s.count, 42, "op1") # int — reads back as int remember(K.s.rate, "3.0", "src", datetime("2019-01-01T00:00:00Z"), datetime("2021-01-01T00:00:00Z")) # valid 2019–2021 remember(K.s.plan, "active", "ops", datetime("2024-01-01T00:00:00Z"), null) # valid 2024 onward, open end ``` The source `src` is a **label**: any non-empty text naming where the value came from — a citation, a register, a URL — read back exactly as written. Two texts that differ in any character are two sources, so `"2026 finance act"` and `"2026-finance-act"` are not one. A few characters are reserved and may not appear in a source. An empty or reserved source written as a literal does not compile; one arriving from data is a refusal you can catch per row, and it names the character it met by its code point. ``` remember(Rate.vat.pct, 25, "2026 finance act") remember(Rate.food.pct, 15, "https://lovdata.no/lov/2009-06-19-58") ``` The writer may also state facts about the claim itself with the one keyword argument, `qualifiers=` — a dict from name to value, after every positional argument: how the value was obtained, how sure the writer is, what it was measured with. Each name follows the rule a path segment does; each value is a `string`, `int`, `bool`, `real`, `datetime`, `duration`, `blob`, `scientific` or `complex`; at most 16. They are the writer's own account, read back as `.qualifiers` (§11.2) on every read that returns the claim, and the store acts on none of them. The same source restating the same value with **different** qualifiers is refused as a conflicting write — use `update` to revise them. ``` remember(Reading.r1.temp, 21, "lab", qualifiers={"method": "observed", "confidence": 0.8}) remember(Reading.r1.temp, 23, "forecast", datetime("2026-10-01T00:00:00Z"), null, qualifiers={"method": "predicted"}) ``` #### `update(K.s.a, v, src)` Supersedes the claim `src` holds at the cell; establishes one on an empty cell. Accepts the same optional trailing `datetime` validity window as `remember`, and the same `qualifiers=` — which is how a source revises what it said about its claim. Restating exactly what `src` already holds there — the same value, qualifiers and window — changes nothing. A windowed `update` supersedes only the claims of `src` whose windows overlap its own: the same value over a different, overlapping window is a revision, and a claim for a period the new window does not reach stands, so one source records a value that changed over time by writing each period with `update`. ``` update(K.s.mood, "playful", "op2") update(Rate.vat.pct, 25, "finance act", datetime("2024-01-01T00:00:00Z"), datetime("2024-12-31T00:00:00Z")) update(Rate.vat.pct, 27, "finance act", datetime("2025-01-01T00:00:00Z"), null) # 2024 still reads 25 ``` **Choosing between them.** Neither verb is a fallback for the other. `remember` asserts something new; `update` says *revise my earlier reading*, and because it also establishes a value on an empty cell it works the first time as well as the tenth. A producer meant to run more than once — a loader, a CI step, anything pointed at data that changes — means `update`, and should ask for it by name. `remember` being Permissive means a re-assert succeeds rather than refusing; it does not make the two interchangeable, because what is lost is a reader of the program being able to tell which was meant. Supersession is per SOURCE, for both verbs. Revising your own claim leaves every other source's claim at that cell standing, so a re-run never quietly settles a disagreement the store is holding — see `prefer` (§5.1) for the verb that records a judgment between sources. To replace another source's value rather than stand beside it, remove that claim with `forget(K.s.a, v, src)`. **A write establishes the kind.** Neither verb requires the kind to exist beforehand: writing `K.s.a` when nothing named `K` has been recorded stores the claim and brings `K` into being. It is the complement of `forget(K.s)` below, which removes the last subject and takes the kind with it — a kind stands for exactly as long as something is recorded under it. Declaring a `class` (§8) fixes the schema a kind's fields are checked against; it is not permission to write, and the two directions are deliberately not symmetric. Within a declared class, writing a field the class does not declare is a `type_error` (§8.3). A write to a kind no class declares is accepted. Knowing which of those you are relying on is worth settling before you build on it: a producer that writes its own bookkeeping under an undeclared kind is using the second, and will keep working; a typo'd field name inside a declared class is the first, and will be refused. A module-qualified path and an unqualified one name different cells. `m.K.s.a` and `K.s.a` are two cells, not one, and a value written through one is not read back through the other. The qualified path is open only to a kind `m` declares with `class` (§9.6). #### `forget(K.s.a)` / `forget(K.s)` / `forget(K.s.a, v, src)` / `forget(K.s.a, datetime("…"))` / `forget(
, recorded_before=datetime("…"))` / `forget()` Permanently removes data. **Hard and irreversible** — the value is physically reclaimed, nothing is kept in history that a read can reach, and nothing in this language puts it back. One qualifier, and it is not a language feature: while a checkpoint stands, a removed value is kept aside where no read reaches it, so the `restore` tool in the [**MCP Reference**](/ref/dke/mcp/) can bring it back — a value that was in force as a new claim, an older version into the history where it stood; a re-assertion, not a rewind. It needs a `read-write-delete` key. With no checkpoint standing at the moment of removal, nothing is kept and nothing can be reinstated. Path-scoped: `forget(K.s.a)` reclaims every value ever recorded at the cell, not just the current one, and `forget(K.s)` removes the whole subject, after which its kind is gone. Claim-scoped: `forget(K.s.a, v, src)` removes the single claim recording value `v` from source `src`, leaving every other claim at that cell untouched — the way to drop one side of a disagreement without discarding the other. Both `v` and `src` are required, because a value alone does not name a claim: two sources may record the same value at one cell. ``` forget(Project.dke.license, "MIT", "registry_one") ``` `v` is matched **by value and by type**, and both halves matter because a cell can hold the same text in two types. `5` and `"5"` are different claims, so `forget(K.s.a, "5", src)` does not reach an integer claim — write the value the way you wrote it, and `forget(K.s.a, 5, src)` reaches it. Every type you can write, you can name: `string`, `int`, `bool`, `real`, `datetime`, `duration`, `blob`, `scientific` and `complex`. ``` remember(Reading.probe.n, 5, "sensor") # an int claim forget(Reading.probe.n, "5", "sensor") # refused - no text claim there forget(Reading.probe.n, 5, "sensor") # removes it ``` Type equality here is **exact**. `==` compares `int` and `real` numerically, so `1 == 1.0` is `True` and a query reaches both — but a removal names a specific claim and takes it permanently, so an `int` literal does not reach a stored `real`. Write the `real` and it does. Naming a claim the cell does not hold is a refuse, not a silent no-op; call `get(K.s.a)` to see the values and sources actually recorded there. Where the value and source match and only the **type** differs, the refusal says so and names the type it found — a read renders `5` and `"5"` identically, so that is the one place the difference is visible. To see a stored value's type yourself, `match` over a claim's `.value` (§7.8). By claim: `forget(c)` removes the one claim `c` — a claim a read returned, or its `.claim_id` — and nothing else. It is how you choose between two claims that agree on value and source and differ only in their periods, which the claim-scoped form cannot tell apart: it removes the first. ``` remember(Rate.eu.vat, 25, "tax", datetime("2020-01-01T00:00:00Z"), datetime("2020-12-31T00:00:00Z")) remember(Rate.eu.vat, 25, "tax", datetime("2022-01-01T00:00:00Z"), datetime("2022-12-31T00:00:00Z")) h = get(Rate.eu.vat, datetime("2022-06-01T00:00:00Z")) forget(h[0]) # the 2022 claim; 2020 stays ``` The claim is looked for in the store as it stands. One removed since it was read, or written inside a `branch` that rolled back, is refused, and so is a `.claim_id` two claims share: nothing is removed, and neither is chosen. A superseded claim is removed like any other. The claim is the whole address, so nothing follows it — write `forget(c)`, not `forget(c.claim_id)`. Time-scoped: `forget(K.s.a, datetime("…"))` removes the values at that cell whose validity window covers the instant named, and leaves the rest. It addresses by **when a value was true** rather than by what it says, so it is the way to drop a period you no longer want to keep without knowing what was recorded in it. The same instant you would pass to `current(K.s.a, datetime("…"))` to read what was in force selects what is removed. ``` forget(Sensor.probe.reading, datetime("2021-06-01T00:00:00Z")) # the values in force then ``` A time-scoped call takes a `K.s.a` path only. An instant narrows which values at **one cell** are named, and a subject holds many cells with many windows rather than one window to narrow; `forget(K.s)` already removes a whole subject and needs no instant to do it. An instant **no value covers** removes nothing and succeeds — unlike the claim-scoped form, which refuses. The difference is what is being named: a claim either exists or it does not, so naming one that is absent is a mistake worth reporting, while a moment nothing was true at is an ordinary answer. Range-scoped: `forget(
, recorded_before=datetime("…"))` trims **history**. It removes the older versions that fall before the instant, and **never a value in force** — whatever an address currently says survives every range, including one whose instant is far in the future. That is the difference between this form and every other one above: the others can empty what they name, and this one cannot. **Three widths.** The range is the only form that takes more than a cell, and that is the point of it — trimming a year of history one cell at a time is not a thing anyone can do: | Address | Trims the history of… | |---|---| | `forget(K.s.a, )` | one cell | | `forget(K.s, )` | every cell of one subject | | `forget(K, )` | every cell of every subject of the kind | Each answers a different number over the same store, and the safety property is the same predicate at all three, so none of them can reach a value in force. A kind-wide range cannot empty anything. ``` forget(Sensor.probe.reading, recorded_before=datetime("2026-01-01T00:00:00Z")) forget(Sensor.probe, recorded_before=datetime("2026-01-01T00:00:00Z")) forget(Sensor, recorded_before=datetime("2026-01-01T00:00:00Z")) ``` `K` is an address **only with a range**. A bare `forget(K)` is refused at compile time and told about this form: emptying a whole kind is not something to reach by leaving a path short. To remove one subject entire, name it — `forget(K.s)`. Four spellings, choosing an axis and a boundary: | Keyword | Removes the older versions… | |---|---| | `recorded_before=datetime("…")` | **written** before that instant | | `recorded_at_or_before=datetime("…")` | written before it, or exactly at it | | `valid_before=datetime("…")` | that stopped **being true** before that instant | | `valid_at_or_before=datetime("…")` | that stopped being true before it, or exactly at it | ``` forget(Sensor.probe.reading, recorded_before=datetime("2026-01-01T00:00:00Z")) ``` The two axes answer different questions and select different versions, so choosing between them is the substance of the call rather than a detail. Ask `recorded_*` when the reason is age — *this was written years ago and I no longer need it*. Ask `valid_*` when the reason is subject matter — *this stopped being true before we cared*. A version whose validity window was never closed has no `valid_*` answer at all and no `valid_*` range reaches it, which is why a `valid_before=` call can report removing nothing where `recorded_before=` would remove several. That is not a failure; it is the two axes disagreeing, which they are entitled to do. **There is deliberately no `after` form.** It would name the versions *newer* than the instant, which includes the value in force — so it would undo part of a cell's history rather than trim its tail, and the safety property above would not hold. Removing a current value is what `forget(K.s.a)` and the claim-scoped form are for, and both say so at the call site. Removing nothing is a success, not a refusal: a cell that has only ever held one value has no older versions to trim, and every range over it reports zero. Use `would_forget` (below) when the difference matters before you act — and at the wider addresses, prefer to. The two-argument forms are told apart by the **type** of the second argument. A `datetime` is the time-scoped form; anything else begins the claim-scoped one and its `src` is still required. Removal **cascades** into facts derived from the target rather than refusing while they exist; a derived conclusion that some other standing rule still supports is re-derived and stays. Needs a `read-write-delete` key. ``` forget(K.s.mood) # the cell and its full history forget(K.s) # the whole subject; its kind is gone forget(K.s.mood, datetime("2024-01-01T00:00:00Z")) # only what was true at that instant ``` **This verb takes no confirmation, and that is deliberate.** The tool surface gates `forget_module` behind `confirm: true` and leaves `forget_rule` and `forget_constraint` ungated, sorting them by whether what they destroy can be brought back: a module's data cannot, a rule's conclusions can, since recompiling the module reinstates them, and a constraint destroys nothing but its own declaration. That sorting has no answer for `forget`, because here it depends on the ARGUMENT rather than on the verb. Three cases, and only the last is beyond recall: - a **rule conclusion** comes straight back — the standing rule re-derives it on the next read; - a fact a **module's top level** wrote comes back on the next `import` of that module, because importing runs the top level again; - a fact **you wrote from your own data** does not come back from anything in the store, and takes its derived conclusions with it. So a blanket confirmation would add a step to two cases that do not need one, and a caller typing it for the third has already decided. What guards an address you cannot afford to lose is `pin` — it refuses `forget(K.s.a)` and `forget(K.s)` while it stands — and it has to be set in advance, which is the honest shape of the guarantee: this surface offers no undo after the fact. #### `assert(K.s.a, src)` Asserts the value at `K.s.a` is **known to be absent** per `src`. No value. ``` assert(K.s.chip_id, "vet") ``` #### `prefer(K.s.a, src)` At a location where sources disagree, states that you go by `src`. Records your judgment beside the claims: every claim stays, nothing is discarded, and the location stays in disagreement. Reads are unchanged — `get(K.s.a)` still returns every claim; each carries `.preferred`, `True` on the one you act on. Standing rules are unchanged too: a preference marks the claim you go by, and does not change what any rule derives. `src` must have recorded a value at `K.s.a`: a location with no value, or a source that recorded none there, refuses as `not_recorded`. Preferring the source already preferred refuses as `already_in_effect` while that preference still covers every source at the location; once another source disagrees there, stating it again is a new judgment over the new set. Naming a different source replaces the judgment. ``` prefer(Sensor.r1.temp, "registry_two") ``` #### `unprefer(K.s.a)` Withdraws the preference at `K.s.a`. Removes the judgment only — every claim at the location survives, as does the disagreement. Where no preference is recorded it refuses: as `already_in_effect` when the location holds a value, as `not_recorded` when it holds nothing at all. ``` unprefer(Sensor.r1.temp) ``` #### `pin(K.s.a)` Protects the address. While it is pinned, `forget(K.s.a)` is refused, and so is `forget(K.s)` — removing the subject would take the pinned cell with it — and so is removing one claim there, by value and source or by the claim itself. The address must hold a value: pinning one that holds none refuses as `not_recorded`, and pinning an address already pinned refuses as `already_in_effect` — catch that one to make sure of a pin. ``` pin(K.s.color) ``` #### `unpin(K.s.a)` Lifts the pin, so the address can be removed again — any pin that stands, whatever the address holds now. Where nothing is pinned it refuses: as `already_in_effect` when the address holds a value, as `not_recorded` when it holds nothing. `list_pinned()` reads what is currently protected, naming each address the way `pin` and `unpin` take it — a kind a module declares qualified by the module — so an address it lists can be written back into `unpin`. ``` unpin(K.s.color) ``` ### 5.2 Read verbs (expression position) #### `current(K.s.a)` → `active_claim` The single live claim, or the `empty` case. Consume with `match` or `.value`. An optional `datetime` reads the value as of another time — past or future: `current(K.s.a, datetime("…"))`. Refuses where no single claim is the answer — a location holding two live values, whether they were recorded there or a rule concluded both. Catch it, or read the set with `get`. ``` cur = current(K.s.color) ``` #### `get(K.s.a)` → `claim_list` The full claim history. Iterable. An optional `datetime` reads the claims valid as of another time — past or future: `get(K.s.a, datetime("…"))`. ``` hist = get(K.s.mood) ``` #### `caveats(K.s)` → `claim_list` The claims at subject `K.s` that are in disagreement — the same question `conflicts()` asks store-wide, narrowed to one subject. An instance is a subject: `caveats(s)` asks it of the instance `s` holds (§8.3). ``` cav = caveats(K.s) ``` #### `conflicts()` → `claim_list` Every claim currently in disagreement, store-wide. (No slot argument.) Two claims disagree when they stand at one location with different values. Who recorded them does not enter into it — a rule that concludes on both sides of a disagreement is one producer, and the location it concludes at is in disagreement like any other. ``` confs = conflicts() ``` Answers which locations are in disagreement. Disagreement is a state the store holds, not an error: both claims remain and reads keep working. The service never chooses between the claims — you say which source you go by with `prefer`, and that judgment leaves the disagreement standing. A standing rule reading a location in disagreement derives from each value that satisfies its condition, so the disagreement travels into the conclusion instead of stopping at the premise; `why` on a conclusion shows the claims it was drawn from. How it clears depends on what disagrees. Between claims you wrote, `forget` one of them, or have one source `update` its claim to the value the other holds. Between **conclusions** those do not reach: a conclusion is in force exactly while its condition holds (§8.8), so `forget` withdraws it and the rule records it again, and `update` adds a claim of your own beside the two already standing. Two rules that both hold and conclude different values for one field is that case, and both conclusions carry the same `derived` provenance, so there is no source to `prefer` between them either. It clears when the rules stop both concluding — narrow one rule's condition, or remove one of them with `forget_rule`. #### `list_values(K.a)` / `list_values(K.a, datetime("…"))` → `claim_list` Every value claim for attribute `K.a` across subjects. An optional `datetime` lists the claims valid as of that time instead of now (§5.4). ``` vals = list_values(K.color) then = list_values(K.color, datetime("2027-06-01T00:00:00Z")) ``` #### `subjects(K.a, v)` → `subject_set` Subjects of kind `K` whose attribute `a` currently equals `v`. The value `v` is a scalar literal (`string` / `int` / `bool`) and the match is **type-aware**: `subjects(K.a, 42)` matches the `int` `42`, not the text `"42"`. Numbers are one family, so an `int` literal also matches a stored `real` of the same number — `subjects(K.a, 5)` finds a `5.0` — and a `bool` is the number it is, so `subjects(K.a, 1)` finds a stored `True`. It is the number that is compared, never its spelling. ``` hits = subjects(K.color, "orange") # text match warm = subjects(K.temp, 42) # int match — not the text "42" five = subjects(K.amt, 5) # finds a stored 5 and a stored 5.0 ``` #### `list_subjects(K)` / `list_subjects(K.a)` → `subject_set` Every subject of kind `K`, or only those carrying a claim for attribute `a`. The two differ whenever a subject is reachable through an attribute its siblings lack — that subject is in the first and not the second. Either form takes an optional trailing `datetime`, listing the subjects a traversal would find at that time rather than now (§5.4). ``` everyone = list_subjects(K) # every subject of the kind coloured = list_subjects(K.color) # only those with a colour then = list_subjects(K, datetime("2027-06-01T00:00:00Z")) ``` #### `list_attributes(K)` / `list_attributes(K.s)` → `string_list` Attribute names for a kind, or for one subject. The two answer different questions and only the second takes an as-of time (§5.4): `K` gives the attribute names recorded for the kind, which describes the store's shape and already covers every time; `K.s` reads one subject's claims, which are what a validity window applies to. An instance is a subject, so `list_attributes(s)` lists the attributes of the instance `s` holds, and takes the as-of time the `K.s` form takes (§8.3). ``` attrs = list_attributes(K) # the kind's attribute names mine = list_attributes(K.s) # this subject's, now then = list_attributes(K.s, datetime("2027-06-01T00:00:00Z")) # …and at that time ``` **A read of a kind that is really a missing import.** A read naming a kind the store does not know refuses. `current`, `get`, `subjects`, the `list_*` reads and `forget` say instead that an `import` is missing when it is: when the name asked for is itself a stored module, and when some stored module declares a class of that name. Each refusal names the module and the `import` that reaches it. Both are gated on the kind being absent, so an enumeration that is legitimately empty still answers empty. #### `list_categories()` → `string_list` Every kind (category) known to the store. A kind a module declares is named in the qualified form `.` — the form the read verbs take, given an `import` of that module (§9); a kind no module declares is named as written. The name a listing gives you is a name the language accepts. Takes no as-of time, for the same reason `list_attributes(K)` does not — a kind is recorded rather than valid, so the answer already covers every time. ``` cats = list_categories() # e.g. ["Loose", "shop.Widget"] ``` ### 5.3 Provenance and reasoning #### `why(K.s.a)` → `proof_tree` The derivation of the current value. The returned `proof_tree` is an **iterable finite collection** of the claims that participated in the derivation: `.length`, `for … in`, and `[i]` yield those claims, each an `active_claim` carrying `.value` / `.source` / `.trust` / `.created_at` / `.path` (§4.1, §11.2). `.root` still yields the top `active_claim` — the queried claim. A participating claim may stand at a different location than the one you asked about — that is what a premise is — so read `.path` rather than assuming. A premise that tested an absence — `absent`, `not_asserted`, a `none(...)` — matched no claim, so it is not an element; the elements are the conclusion and the claims `premises` (below) lists. The `proof_tree` a `why` yields is the derivation alone, the conclusion and then the claims it rests on; the earlier, superseded values of the cell are no longer elements of it, and remain readable through `get`. A `known_absent` premise read a recorded absence, which is a claim, and is one. A derivation can run deeper than a proof is rendered. `.truncated` is `True` when the answer stops short of the whole derivation, and `False` when what you have is all of it. Check it before treating a proof as complete. ``` pt = why(K.s.color) n = pt.length # how many claims participated r = pt.root # the queried claim first = pt[0] # a participating claim → active_claim for c in pt: # iterate the derivation's claims print("via " + str(c.source)) ``` **`why(K.s.a, datetime("…"))` — the derivation as it stood then.** An optional trailing `datetime` (§5.4) answers with the premises that were in force at that instant rather than now. A conclusion rests on claims that carry validity windows (§5.1), so it can hold at one time and not another; the anchored form is what reads back the derivation that used to hold. This is how you account for an answer that **changed**. Ask the same cell at two instants, and the difference between the two trees names the premise that moved: ``` before = why(Order.o1.review, datetime("2024-03-01T00:00:00Z")) after = why(Order.o1.review, datetime("2024-09-01T00:00:00Z")) for c in after: print("now rests on " + str(c.path) + " = " + str(c.value)) for c in before: print("then rested on " + str(c.path) + " = " + str(c.value)) ``` An anchored derivation may be absent where the bare one is not, and the reverse. Absence here is the `empty` discriminator (§4.1), not a zero-length `proof_tree` — so `.length`, `[i]` and `for … in` are for an answer you have established is a proof, with `match` as for a read: `case proof_tree:` for a derivation, `case empty:` for none (§7.8). It means no conclusion was derivable at that instant, which is a different finding from a conclusion that is derivable and false. When there is no derivation, `why_not` is the verb that explains the absence. #### `why_not(K.s.a)` → `diagnosis` Why a conclusion is **not** there. `why` needs a claim to explain; when a read comes back empty the question is a different one, and this answers it — naming the condition that blocked the conclusion, at the cell you can act on. It separates cases that every other verb renders identically. An empty read looks the same whether a condition was never recorded, was recorded with a validity window that does not cover the time you asked about, was recorded and fails its test, was recorded as absent with `assert`, was recorded with a value that has no order under `<`, `<=`, `>` or `>=` (a blob, or a recorded absence), is compared under one of those with a value that has no order, is required to be absent and is present, or is recorded by sources that disagree. A condition comparing two of your fields, such as `o.qty > o.limit`, is stated with both, as you wrote it. Those need opposite fixes, so the answer says which one you have — recording a fact you already hold is not a weaker version of the right move, it is the wrong one. When no standing rule concludes the cell at all, that is its own answer — your model has no path to the conclusion, which is a different problem from an unmet condition and usually a more useful thing to learn. When a rule has every condition it needs and still concludes nothing, the answer names that rule and the reason, which is one of three. A fold it computes cannot take the values it reads — a `string` where it sums; `count` takes any value. A comparison it concludes cannot rank the values it reads — a value with no order, or two values of different types. Or the arithmetic it computes has no answer for them — a divisor of zero, a value its operator does not take, or a result its type cannot hold — and the answer shows that operation over the values it read, such as `6 / 0`, with the fields they came from. None of those is a fact to record, and waiting draws nothing: the move is changing those values. `what_needs` says the same of that rule, before the conditions it names as recorded. A condition is named at its cell as your rule reads it, with its value spelled the way you write one — `True`, `"5"`, `blob("00FF")`. A subject or field the rule leaves open is named by your rule's own name for it, in backticks: `` E[`e`].b == "z" `` is a condition any `E` can meet, the rule's `e`, and not a subject called `e`. Where the rule reads a condition's subject out of a field your sources disagree on, the condition is named once for each subject they give, each at its own cell, and `what_needs` does the same. A condition whose subject the rule leaves open names no one cell, so it is answered for its whole kind. It is recorded and failing when some `E` holds that field and none meets the test, and nothing is recorded only when no `E` holds it. When each of the rule's conditions on that subject is met by some subject, and no one subject meets them all, the answer says so, and names the conditions together. That is one finding, and its fix is on one subject rather than a missing fact. It names no subject, and it is the same whichever order you wrote the conditions in. A condition that needs such a field absent is answered the same way: some `E` holds it. The cell you ask about is checked before any of that. If it holds a claim whose validity window does not cover the time asked about, you are told so and given the value — the fact is there and out of force, and what it needs is a wider window or an as-of the window covers, not another write. Whatever the rules have to say follows it, so you get both. When the conclusion does hold, you are told so and pointed at `why`. The result is an iterable finite collection of lines: printing it shows the whole explanation, iterating it takes one line at a time. It is a read and writes nothing, and it takes no `as_of` — a diagnosis is about the store as it stands. This verb exists only in English. A unit compiled under another `language` cannot call it — the English spelling is not a verb there — and the remedy is the unit's `language` tag rather than an edit to the program (§12). ``` d = why_not(Device.b.online) print(d) # rule `device_is_online` needs Device[b].checks > 0, # and nothing is recorded there win = why_not(Device.w.online) print(win) # rule `device_is_online` needs Device[w].checks > 0, # and Device[w].checks is recorded as 2 but not in # force at the time asked about n = d.length for line in d: print(line) ``` A rule over a subject it leaves open, where fern is thirsty and in the north bed, and cactus is in the south bed and not thirsty: ``` bed = why_not(Bed.south.water_on) print(bed) # rule `open_valve` needs Plant[`p`].thirsty == True # and Plant[`p`].bed == "south" together: each is met # on its own, but no one `p` meets all of the rule's # conditions at once ``` #### `what_needs(K.s.a)` → `diagnosis` What would make a conclusion hold. Where `why_not` names what is blocking now, this names the facts that would unblock it — walking through conditions your standing rules can themselves derive, and stopping at the ones only you can supply. Each line says which it is, so the answer is a list of things to do rather than a description of the problem. A condition you already recorded, whose validity window does not cover the time asked about, is named as that rather than as a fact to record — what it needs is a wider window or a different as-of, not another write. So is a condition whose value you recorded and which does not meet the test, has no order under `<`, `<=`, `>` or `>=`, is compared under one of those with a value that has no order, or is recorded by sources that disagree: each is named as recorded, with what is wrong with it, and only a condition with nothing recorded is a fact to record. A condition recorded as absent with `assert` is named as that, since the rule needs a value there and the absence you recorded is not one. A condition whose subject the rule leaves open is answered for its kind, as `why_not` answers it, and a condition met on its own that no one subject meets together with the rule's other conditions is named as that. A condition that needs a field absent, such as `absent(c.blocked)` or a `none(...)`, is the other way round. When the field holds a value it is named as present, with the move that clears it. Where you recorded the value, that is removing it or narrowing its window. Where a rule concludes it, the move goes through that rule, since a conclusion `forget` removes is drawn again while its premises hold, and `why` shows what it rests on. It is never a fact to record, and the rules that would conclude it are not walked into, since what they need would make it hold. A condition that needs a field absent and has it absent is listed as held. The walk is bounded by the shape of your rule set: each rule is entered once per question, so a rule set that refers to itself in a circle reports the circle rather than following it. Like `why_not` it is a read, takes no `as_of`, and returns a `diagnosis`. Like `why_not`, this verb exists only in English: a unit compiled under another `language` cannot call it, and the remedy is the unit's `language` tag rather than an edit to the program (§12). ``` d = what_needs(Device.b.online) print(d) # via rule `device_is_online`: # have Device[b].power # need Device[b].checks > 0 (record this) win = what_needs(Device.w.online) print(win) # via rule `device_is_online`: # have Device[w].power # need Device[w].checks > 0 (recorded, but not in # force at the time asked about) ``` Over the same plants: ``` bed = what_needs(Bed.south.water_on) print(bed) # via rule `open_valve`: # need Plant[`p`].thirsty == True (met on its own, # but not together with the rule's other conditions) # need Plant[`p`].bed == "south" (met on its own, # but not together with the rule's other conditions) ``` A rule that needs `c.blocked` and `c.jammed` absent, where `c1` has `blocked` recorded, and another rule concludes `jammed` for `c2`: ``` one = what_needs(Crate.c1.ok) print(one) # via rule `ok_when_clear`: # have Crate[c1].x # need Crate[c1].blocked to be absent (it is present # — remove it, or narrow its window) # have Crate[c1].jammed absent two = what_needs(Crate.c2.ok) print(two) # via rule `ok_when_clear`: # have Crate[c2].x # have Crate[c2].blocked absent # need Crate[c2].jammed to be absent (it is present, # and a rule concludes it — `why` shows what it # rests on) ``` #### `verify(K.s.a, v)` / `verify(K.s.a, v, src)` → `verify_result` Checks whether `K.s.a` currently holds `v` (optionally per `src`). The value `v` is a scalar literal (`string` / `int` / `bool`) and the check is **type-aware**: `.match` is `True` only when the stored value and its type agree with `v` — except that `int`, `real` and `bool` are one numeric family, so a stored `5.0` matches the literal `5` and a stored `True` the literal `1`. Fields `.match` (`bool`), `.actual`, `.expected` (`string`). ``` v = verify(K.s.color, "orange") if v.match: print("confirmed") ``` #### `agreement(K.s.a, v)` → `int` Returns how many **distinct sources** recorded `v` at `K.s.a` — the quantitative sibling of `verify` (verify asks *is `v` present?*, agreement asks *how many sources say `v`?*). Type-aware, like `verify`. A count of corroboration, **not a judgment of truth**: the language reports how many sources recorded the value and never ranks, certifies, or assumes the independence of those sources — which ones to trust is the caller's judgment (read them by name with `get`). Counts every source, so it takes **no** source argument (a type_error); an unrecorded value reports `0`. ``` orange = agreement(K.s.color, "orange") # e.g. 2 ``` #### `would_forget(
, recorded_before=datetime("…"))` / `would_forget()` → `int` Returns how many older versions the matching **`forget` range would remove** — and removes none of them. Ask what a removal costs before paying for it. It takes the same four keywords as the range form of `forget` (`recorded_before` / `recorded_at_or_before` / `valid_before` / `valid_at_or_before`) and the **same three addresses** — `K.s.a`, `K.s`, `K` — so every removal you can write has a preview you can write. With an address, one of the keywords is **required**: there is no bare `would_forget(K.s.a)`, because previewing an unbounded removal only ever answers *everything here*, which needs no instrument and would make the one call worth checking look as though it had been checked. ``` n = would_forget(Sensor.probe.reading, recorded_before=datetime("2026-01-01T00:00:00Z")) if n > 100: print("that would drop a lot of history") else: forget(Sensor.probe.reading, recorded_before=datetime("2026-01-01T00:00:00Z")) ``` It is a **read**, so a read-only key can call it: finding out whether you want to remove something should not require holding the ability to remove it. It records nothing — a preview leaves no trace in the removal record, which stays a record of what was actually destroyed rather than of what was considered. The number the preview gives is the number the removal reclaims, at whichever width you asked — the two read the same selection rather than agreeing by convention, so a preview taken at `K` and a removal run at `K` cannot disagree. `would_forget(c)` previews `forget(c)`, and takes no keyword: it names one claim rather than a range. It returns `1` while the store holds the claim `c`, and refuses as `forget(c)` would when it does not. `0` means the range names nothing, which is the ordinary answer at a cell with no history to trim, and it is worth distinguishing from a `valid_*` range that finds nothing because the versions carry no closed validity window (see `forget` above). Asking the `recorded_*` question as well is how you tell those two apart. #### `writers(K.s.a, v)` → `int` Returns how many **distinct store keys** wrote `v` at `K.s.a` — `agreement` on the other provenance axis. `agreement` counts what was **cited**; `writers` counts who **wrote**. The difference matters: three sources cited by one key is corroboration one party assembled, while one source written by three keys is three parties independently recording the same thing. Type-aware and judgment-free like `agreement`, and it takes **no** source argument (a type_error). A claim written outside an authenticated call has no key and is not counted — so `writers` can be lower than `agreement` at the same cell, and reports `0` where every claim was written unauthenticated. ``` a = agreement(K.s.color, "orange") # 2 — two sources cited w = writers(K.s.color, "orange") # 1 — but one key wrote both ``` #### `dependents()` → `claim_list` The claims concluded directly from a claim — one step down its downstream in the dependency graph. The operand is a claim read from `get`/`current`, or an element of an earlier `dependents` (bind it to a name first), or a claim's `.claim_id` (§11.2), which names the claim whatever has been written since it was read. A claim the store no longer holds is refused as a `refuse`, whether passed as the claim or as its `.claim_id`: a conclusion withdrawn since it was read, or one a `known_at=` read worked out again. So is a `.claim_id` two claims share; a string that is not a `.claim_id` is an error. Each element is the dependent claim itself — its value, its trust, and `.derived_by` naming the rule — so it can be walked a further step with `dependents` or back with `premises`. A base fact with no rules firing on it has no dependents (an empty `claim_list`). With `transitive=True` it answers every distance at once: the claims concluded from the claim, from those, and so on down, each listed once, in name order. It answers in full on every store, one where two conclusions each rest on the other included. The keyword takes a `bool` and is given at most once; `transitive=False` is the one-step read. ``` base = get(K.s.a) for c in base: deps = dependents(c) for d in deps: print("derived cell = " + str(d.path)) # .path is the dependent's K.s.a below = dependents(c, transitive=True) # everything resting on c ``` #### `premises()` → `claim_list` The claims a conclusion was drawn from — one step up, the mirror of `dependents`: the claims its rule read, or the fields a computed field reads. The operand is a claim read from `get`/`current`, or an element of a `proof_tree` or of `dependents` (bind it to a name first), or a claim's `.claim_id`, refused as `dependents` refuses it, a claim the store no longer holds included. A claim someone recorded has no premises (an empty `claim_list`). A conclusion whose validity window does not cover the present still names its premises, read at a time inside its window; where two rules conclude different values at one cell, each claim answers its own. The claim's `.derived_by` names the rule that drew the conclusion (§11.2). ``` t = current(Order.o1.total) # total: int = price * qty print(t.derived_by) # Order.total ps = premises(t) print(str([p.path for p in ps])) # ['Order.o1.price', 'Order.o1.qty'] ``` ### 5.4 Reading as of a time Six read verbs accept an optional trailing `datetime` (§4.1) naming the time the read is answered **as of**: | verb | anchored form | what it answers at that time | |---|---|---| | `current` | `current(K.s.a, datetime("…"))` | the single value active then | | `get` | `get(K.s.a, datetime("…"))` | the claims valid then | | `why` | `why(K.s.a, datetime("…"))` | the derivation as it stood then (§5.3) | | `list_subjects` | `list_subjects(K, datetime("…"))` / `list_subjects(K.a, datetime("…"))` | the subjects a traversal finds | | `list_values` | `list_values(K.a, datetime("…"))` | the values recorded at the attribute | | `list_attributes` | `list_attributes(K.s, datetime("…"))` | the attributes the subject carries | Omitting the argument reads the present, so a bare call and its anchored form differ only where a validity window (§5.1) is in play — a fact written without one is valid at every time, and reads the same either way. **The time may be future as well as past.** A commitment written for later — a release date, a scheduled rate change, a contract that starts next quarter — is not part of the present, so nothing at the present reports it. The anchor is what reads it back, and on the three enumerations it is what finds it at all: without one, a store can hold a fact that no traversal reaches. ``` remember(Release.v2.ships, "2027-03-01", "roadmap", datetime("2027-01-01T00:00:00Z"), datetime("2027-12-31T00:00:00Z")) when = datetime("2027-06-01T00:00:00Z") list_subjects(Release, when) # ["v2"] list_attributes(Release.v2, when) # ["ships"] list_values(Release.ships, when) # the claim current(Release.v2.ships, when) # "2027-03-01" ``` **`list_attributes(K)` and `list_categories()` take no as-of time.** The first gives the attribute names recorded for a kind, the second the kinds recorded in the store. Both describe what the store **holds** rather than what is true at a moment, so each already covers every time and an anchor could only narrow a complete answer. This is why `list_attributes` takes one in its `K.s` form and not in its `K` form — the first reads a subject's claims, the second a kind's vocabulary. Offering a time where none is accepted is a compile error naming the form that takes one. **Reading as of what the store knew.** The same six reads take one keyword argument, `known_at=`, a `datetime` on the other time axis: not when a fact was true of the world, but what the store had been told by then. A claim written later is not among the answer. A claim revised or replaced later is, with the standing it had then, so a disagreement the store held at that moment reads as one, and `current` refuses over it as it would over a disagreement now. ``` remember(Price.widget.eur, 10, "list") # written in January update(Price.widget.eur, 12, "list") # written in March feb = datetime("2026-02-01T00:00:00Z") current(Price.widget.eur, known_at=feb) # 10 — what the store held then current(Price.widget.eur) # 12 ``` The two times are independent and may be given together: `current(K.s.a, datetime("T1"), known_at=datetime("T2"))` is the value the store, as of T2, held to be in force at T1. Without `known_at=` a read answers over everything the store holds now, and a `known_at=` later than every write answers the same way. `forget` is retroactive: what was forgotten was never known, as far as any read can tell. A conclusion is answered as it stood at that moment: what the rules, as they read then, concluded from what the store had been told by then. That includes a conclusion withdrawn since, and leaves out any that only a rule written later concluded. A conclusion in such an answer may carry an empty `.created_at`, since when it was recorded is not kept once what it rests on has changed. A `known_at=` read refuses catchably in three cases: | reason | when | what to do | |---|---|---| | `derived_field` | the answer rests on a rule whose text at that moment the store does not keep: a rule changed after that moment, or a rule whose earlier text was never recorded | read it without `known_at=`, or as of a moment after the change; a rule of the second kind takes part once it is compiled again, unchanged, for a read as of any moment from then on | | `not_recorded` | a value the answer rests on, at the address or at one a rule reads to reach it, was replaced by one that has since been forgotten, so when it stopped being in force is no longer recorded | read it without `known_at=`, or read another address at that time | | `limit_exceeded` | the answer rests on more of the store than a read as of an earlier moment may work over; the same read over the same data refuses the same way every time | read it without `known_at=` | `known_at=` is a keyword, never a position, and is given at most once. Only these six reads accept it — on any other verb, `forget` included, it is a compile error — and its value must be a `datetime`. ### 5.5 Result-type introspection #### `list_result_types()` → `result_type_set` The result types **a program can hold** — these nine, in this order: ``` rts = list_result_types() # active_claim, claim_list, diagnosis, empty, proof_tree, # result_type_set, string_list, subject_set, verify_result ``` Eight are also type names you may write in an annotation. `empty` is the ninth and is not one — it is the sentinel case of an `active_claim` (§4.1) — and it is listed because a program can hold it, which is what this verb asks. **These names are canonical.** They are the same strings `.kind` gives you (§11.2) and the same `case` labels a `match` compares against. The order is ascending by canonical name. This is not the set of `result.type` discriminators the wire envelope can carry. That set is a different one: it has no `diagnosis` and no `verify_result`, and it does carry `handle_result`, `change_event_list` and `program_output`, which no program binds. ### 5.6 Script-data reads (in-language) Read-only, so they **run inside a program**. #### `list_scripts()` → `string_list` Bare names of every stored script. ``` names = list_scripts() ``` ### 5.7 Wire-only verbs (not callable in a program) Callable at the `dke.langsyn.net` wire surface but **rejected in a program body** — they mutate the script set or answer administrative provenance. Each row is an MCP tool call, with the tool's own argument names. The table is a selection: the tools that change or describe your modules and rules. [MCP Reference §2](/ref/dke/mcp/#tool-surface) lists every tool the wire serves. | Tool call | What it does | |-----------|--------------| | `compile(name, source)` | provide + store + **run** a module; its `def`s become callable by name (§7.11) | | `compile(name)` | reload a stored module by name — re-run its body (§7.11) | | `run(source)` | **execute instructions WITHOUT storing anything** — "try it" to `compile`'s "keep it" ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). A program reads, writes, and calls compiled scripts; a definition (`def` / `class` / `@rule` / computed field) belongs to `compile`. To call stored code: `import ` then `.(...)` | | `list(target = scripts)` | list signatures (also runs in-language, as `list_scripts()`) | | `forget(name)` | remove a script (idempotent); `name` is `module.script` | | `info(name)` | compile provenance; `name` is `module.script` | | `list(target = rules)` | list the names of the standing rules you wrote with `@rule`, each with its kind when it has one — authored-only, so a computed field is not listed (§8.8) | | `list_modules()` | list stored modules — name, script count, created-at ([MCP Reference §9.4](/ref/dke/mcp/#modules-address-by-bare-module-name)) | | `info_module(name)` | a module's record — init language, its `module.script` names, the classes it declares, its `version` if it has one, updated-at, created-at; `name` is BARE ([MCP Reference §9.4](/ref/dke/mcp/#modules-address-by-bare-module-name)) | | `forget_module(name, confirm = true)` | delete a whole module + its scripts, kinds, and data; needs `confirm`; refuses on a live cross-module reference; `name` is BARE ([MCP Reference §9.4](/ref/dke/mcp/#modules-address-by-bare-module-name)) | | `forget_rule(name)` | remove ONE standing rule and withdraw what it derived, leaving the rest of its module in place; `name` is the `module.rule` name from `list(target = rules)` | | `checkup()` | report your store against the standing rules over it — which premise cells that need a value hold nothing, which of your kinds no rule reads; counts every standing rule in force, computed fields included (§8.8) | The in-language `import ` statement (§7.11) loads a stored module inside a program body and so is **not** listed here. `compile` and `run` are the two ways to **submit a source**, and they differ in one respect: `compile` **stores** the compilation (the module's `def`s become callable by name, its rules stand); an inline `run` **executes it once and stores nothing** — data it `remember`s persists, but no script is left for later instructions to call or for `list` to report. Neither needs a `def`: bare top-level statements are a complete program, which is the shortest form for a one-off question (such a program takes no arguments — a `def` is what gives it parameters). Any number of procedures run (they may call one another; the last top-level `def` is the entry); a source that defines standing rules (a `@rule` or a computed field) cannot take effect in a `run` and is refused — such a source is `compile`d to activate its rules. Both report what the module body did — its output and its store effects — since both run it, and `compile` additionally states what it installed. → §9.3, [MCP Reference §4.1](/ref/dke/mcp/#compile-validate-and-store-a-module), [§4.2](/ref/dke/mcp/#run-execute) ### 5.8 Aggregate query functions A read-only fold over a whole **attribute** — one attribute across every subject of a kind — returning a `value` (§4.1). Bind it to a name on its own line, then use it, or return it whole (`return avg(Reading.value)`); an aggregate may **not** be a bare argument to another call. ``` n = count(Reading.value) # single-attribute fold p90 = percentile(Reading.value, 90) # ranked fold, rank 0–100 slope_xy = slope(Point.x, Point.y) # two-attribute fold (both attributes of one kind) mean = avg(Reading.value) # bind first, then use `mean` ``` - **Single-attribute** `fn(K.attr)` — `count`, `sum`, `product`; `avg`, `median`, `midpoint`, `mode`, `geomean`, `harmmean`, `rms`; `min`, `max`, `span`, `variance`, `stdev`, `iqr`, `mad`, `cv`; `skewness`, `kurtosis`, `entropy`. - **Identity / positional** `fn(K.attr)` — `distinct` (how many distinct values, counting equal numbers once as a Python `set` does: `1`, `1.0` and `True` are one value), `argmin` / `argmax` (which subject holds the min / max value), `first` / `last` (the earliest- / latest-recorded current value). - **Ranked** `percentile(K.attr, )` — the ``-th percentile (rank 0–100). The rank is an integer literal or the name of an `int`, resolved at runtime; outside 0–100 it is refused, at compile time for a literal and at runtime for a name. An expression is bound to a name first. - **Two-attribute** `fn(K.x, K.y)` (both attributes of one kind) — `covariance`, `correlation`, `slope`, `intercept`, `rsquared`. Anything after a single-attribute fold's attribute is refused at the fold's name, which gives its form, and so is a two-attribute fold's second operand that is not an attribute, such as `x + 1`. A keyword argument is refused at the keyword, since an aggregate query takes none; `key=` and `default=` on `min` and `max`, and `start=` on `sum`, are named as what those names take over a list. **Which definition each name denotes.** Some of these names mean a different number in different systems, so the choice is stated rather than left to the reader: `variance` and `stdev` are the **population** figures (divide by `n`); `skewness` is the population `g1`; `kurtosis` is **excess** kurtosis (`0` for a normal distribution); `mad` is the **median** absolute deviation about the median; `entropy` is in **nats**; `percentile` and `iqr` interpolate **linearly** between the two nearest ranks; and `covariance` is the **population** covariance, where Python's `statistics.covariance` is the sample one. `stdev` is worth a second look if you come from CPython. There, `statistics.stdev` is the SAMPLE standard deviation and `pstdev` is the population one; here `stdev` is the **population** figure. Over `[2, 4, 4, 4, 5, 5, 7, 9]` this answers `2.0` where `statistics.stdev` answers `2.138090` — both correct for their own definition, which is why the name alone does not tell you which you have. **Rounding over `real` values.** A fold has no order to add its values in, so it does not add them as a loop over a list does. `sum` is the **exactly rounded** sum, Python's `math.fsum`, and `avg` is that sum over the count, as `statistics.fmean` computes it; `rms` is `math.sqrt(statistics.fmean([x * x for x in xs]))`, and `geomean` is computed as `statistics.geometric_mean` computes it. Over ten values of `0.1`, `sum` is `1.0`, as `sum([0.1] * 10)` is. `min`, `max`, `median`, `midpoint`, `span`, `mad`, `iqr` and `percentile` answer what the same formula answers over a list. The other folds are computed in an order of their own and can differ from a Python computation in the last place ([Python Specification §7.1.1](/spec/dke/python/#aggregate-query-functions)). Where the Python computation passes the largest `real` on the way and the fold's value does not, the fold answers its value: over `1e308` and `1.5e308`, `avg` yields `1.25e+308`, where `statistics.fmean` raises. A value past the largest `real` yields `inf` or `-inf`. The result's class follows the attribute (a numeric attribute yields a number; a `datetime` / `duration` attribute a `datetime` / `duration`) — except `distinct` (always an `int`) and `argmin` / `argmax` (always a `string`, the subject); `first` / `last` follow the attribute. Over values holding a `real`, a fold that computes yields a `real`, whole or not (`sum` over `0.5`, `1.5` is `2.0`). Over `int` and `bool` values alone, `sum`, `avg`, `span`, `midpoint`, `variance` and `product` yield an `int` when the result is whole (`avg` over `1`, `3` is `2`), and `stdev`, `iqr`, `percentile`, `mad`, `cv`, `geomean`, `harmmean`, `rms`, `skewness`, `kurtosis`, the two-attribute folds and an even-count `median` yield a `real` (`stdev` over `1`, `3` is `1.0`). A `bool` attribute is numeric: a fold that computes yields a number (`sum` over `True`, `True`, `False` is `2`), and `min`, `max`, `mode`, `first` and `last` yield the stored value (`max` is `True`), the first recorded of equal values, or for `last` the last. Destructure with `match` or use it wherever a `value` is expressible. Fold names are **soft keywords** — recognised only right before `(`, so they stay ordinary identifiers elsewhere. An aggregate over a attribute with **no data** is a `refuse`; a value-comparing fold (`sum`, `avg`, `min`, `max`, `argmin`, `argmax`, …) over an attribute whose values are **not numbers** (a text attribute, or one mixing numbers with non-numeric values) is likewise a `refuse` — the fold declines rather than treating a non-number as zero. So is one over an attribute mixing two of the numbers, `duration`, `datetime` and `scientific` (a `duration` beside an `int`), as Python's `sum` and `max` raise. The folds that don't compare values numerically — `count`, `mode`, `entropy`, `distinct`, `first`, `last` — read how the values are distributed, or return a stored value unchanged, so a `string`, `bool`, `datetime`, `duration`, `scientific` or `complex` attribute folds under all six without refusing on that ground (a `complex` has no order, so the folds that compare by value, `sum` among them, refuse one), and an attribute mixing them folds too, each chosen value in its own class. A `blob` value is the exception: `count` folds an attribute holding one, and the other five refuse over it — a blob is opaque, so there is nothing in it to group by, to tell apart, or to hand back as a result. ### 5.9 Worlds A **world** is a named alternative to the store, kept until it is merged or discarded. It holds everything the store holds, and what is written in it is seen only there. A program writes in a world and reads it inside a `with world(...)` block (§7.18), and a later program can enter the same world again, so one alternative can be built up and read over as many runs as it takes. A world's name is a `string` and follows the rule a branch's name does (§7.10). #### `create_world(name)` (statement) Creates the world, holding nothing of its own yet. A world of that name that already exists is a `refuse` (`already_in_effect`). #### `list_worlds()` → `string_list` The worlds that exist, in name order. It answers anywhere, inside a `world` block too. #### `compare_worlds(a)` / `compare_worlds(a, b)` → `claim_list` The claims world `a` holds that the store does not hold, or that world `b` does not hold, in name order, each an `active_claim` with the fields every claim carries. A claim of `a` is left out when the other side holds an equal one: the same field, value and type, source, validity window and qualifiers. A claim since revised or replaced is history and is not compared. A validity window is compared, not used as a filter, so a claim valid only in the past or only in the future is compared like any other. A rule's conclusion is compared like any other claim, so where two worlds' facts differ, the conclusions drawn from them are listed too. Everything the store holds is held in every world, so `compare_worlds(a)` is what `a` adds to the store, and asking in both directions answers every difference. ``` d = compare_worlds("wet", "dry") for c in d: print(c.path, c.value, c.source) ``` #### `merge_world(name)` (statement) Makes the world's writes the store's own, and removes the world. Where the store and the world each hold a claim of one source at one field, the one written later stands, as a revision by that source would. The store's rules then run over the merged facts. The store holds the merged writes from the moment of the merge. A read as of an earlier time (`known_at=`) answers without them. Each keeps, as its `.created_at`, the time it was written in the world. A value written in the world at a field one of the store's rules concludes, where the rule does not yield to a recorded value (§8.8), makes the merge a `refuse` (`asserted_field`) naming the value and the rule. Nothing is merged, and the world keeps everything written in it. Forget the value inside the world, or forget the rule, and merge again; or discard the world. Where the store already holds a conclusion at that address, forget the world's value by its value and source, `forget(K.s.a, v, "src")`: forgetting the whole address would reach the store's conclusion too, which a world cannot remove. #### `discard_world(name)` (statement) Removes the world and everything written in it. It is a removal, so it needs a key with the delete grant, as `forget` does. **Keeping a world current.** What the store gains reaches a world when the world is entered, compared or merged, and the world's rules then draw their conclusions over the store's newer facts. What the store loses reaches every world at once: when a fact the store holds is forgotten, revised or replaced, or a rule is removed, a conclusion a world drew from it is withdrawn in that world too. **What a world cannot change.** A world adds to the store and never changes what the store held before it. An `update` inside a world revises a claim the store holds as it would outside (§7.10), and what the store's rules concluded from that claim no longer reads in the world; the store still holds both. A fact the world writes does the same to what the store's rules concluded from that fact's absence. Inside a world, forgetting a claim or a rule the store holds is a `refuse` (`immutable_in_branch`), and so is `prefer` or `unprefer` at a field the store has judged. A field's judgment is held in one place at a time, so `prefer` at a field another world has judged is a `refuse` (`still_referenced`) until that world is merged or discarded. **Where the verbs are reached.** `create_world`, `merge_world`, `discard_world` and `compare_worlds` are refused inside a `world` or `branch` block. Naming a world that does not exist is a `refuse` (`not_recorded`), and so is a name that is not one identifier. --- ## 6. Operators DKE Python uses **word-form logical operators** (`and` / `or` / `not`), the comparisons `== != < > <= >=`, the arithmetic / concatenation operators `+ - * / %`, the bitwise operators `& | ^ << >> ~` on integers (§6.3), and the set operators `| & ^` (§6.10). An operator given operands it does not take is a type error. Where CPython refuses the same operands, each of a class it has, the refusal is CPython's sentence, naming CPython's classes: `"a" < 1` is refused with `'<' not supported between instances of 'str' and 'int'`, `[1] + 5` with `can only concatenate list (not "int") to list`, and `x += "a"` over an `int` with `unsupported operand type(s) for +=: 'int' and 'str'`. Where an operand is a `value` read from a claim, a `datetime`, a `duration` or an instance, the refusal names the operand types the operator takes. A `range` is a `list`, so a refusal names it `list`. ### 6.1 Comparison `==` `!=` over any two values. Values of different types are **unequal**, as in Python — `"7" == 7` is `False` and `"7" != 7` is `True`, and nothing is converted to be compared. Numbers compare by value across `int`, `real` and `bool`, or an element of a list mixing them (§6.7): `1 == 1.0` is `True`, and a `bool` is the `int` it is, so `True == 1` is `True`, matching the ordered comparisons and Python. A `value` compares with any value as the class it holds (§4.1), and two discriminators compare `.kind`. Two constructed lists compare element-wise at any depth — `[1] == [1.0]` is `True`, `[1] == ["a"]` is `False`, and an empty list compares with any list (§6.7). The same rule holds wherever a value is looked for: `in`, `.index`, `.count`, `.remove`, a dict key read, set inclusion and `is` (below). Two operands are refused: a class instance, and an answer read from the store (a claim, a proof) against anything but its own type. `<` `>` `<=` `>=` over numeric operands (`int`, `real`, `bool` or `scientific`, or an element of a list mixing them, §6.7; mixed allowed — compared numerically) **or** two strings. A `scientific` orders by its magnitude, the `real` nearest the number it writes, against any number or another `scientific`, as a rule premise orders one (§8.8): `scientific("2.0e4") > 100` is `True`. Its figures take no part, so `scientific("6.022e23")` and `scientific("6.0220e23")` are neither `<` nor `>` each other, while `==` still tells them apart, and tells `scientific("1.0e0")` from `1`. A `complex` equals another when both parts are equal, and equals a number exactly when its imaginary part is zero and its real part equals that number (`1 + 0j == 1` is `True`). It has **no order**: `<` `>` `<=` `>=` with a `complex` on either side is a type error in Python's words (`'<' not supported between instances of 'complex' and 'int'`), and so are `sorted`, `min` and `max` over one. Order by a part or by the magnitude instead: `sorted(zs, key=abs)`. A string comparison is **lexicographic** by Unicode codepoint, so `"apple" < "banana"` and (uppercase first) `"Z" < "a"`, matching Python; a shorter string that is a prefix orders first (`"a" < "ab"`). A string compared against a number is a type error. `<` `>` `<=` `>=` also order two **datetimes** (chronologically — the earlier instant first) or two **durations** (by length — the shorter first). Both compare by value, not spelling: `duration("PT90M") < duration("PT1H30M")` is `False`, and so is `>`, since the two are the same length; a datetime written with fractional seconds orders against one written without them by the instants they denote. A datetime compared against a duration, a number, or a string is a type error. `datetime` and `duration` also COMPUTE together: `datetime + duration` (either order) and `datetime - duration` shift an instant, `datetime - datetime` gives the length between two instants, and `duration + duration` / `duration - duration` add and subtract lengths. A `duration` also scales: `duration * ` (either order) stretches it (`duration("PT1H") * 2.5` is `PT2H30M`) and `duration / ` splits it (`duration("PT1H") / 4` is `PT15M`). Dividing a `duration` by another `duration` asks how many times one fits into the other and gives a plain `real` — `duration("PT3H") / duration("PT45M")` is `4.0`, not a length — while `%` gives the length left over (`duration("PT3H10M") % duration("PT45M")` is `PT10M`). Shifts respect the calendar (`datetime("2024-02-28T12:00:00Z") + duration("P1D")` is `2024-02-29T12:00:00Z`). Addition, subtraction, `%` and scaling by an `int` are exact to the nanosecond; scaling by a `real` rounds to the nearest nanosecond, halves away from zero. Subtracting a later instant from an earlier one yields a negative `duration`, which renders and reads back with a leading `-` (`duration("-PT2H")`). Two datetimes have no sum, `duration - datetime` is meaningless, `duration * duration` is an area rather than a time, ` / duration` is a frequency rather than a time, and `//`, `**` are undefined over temporal values; a span past ~±292 years, an instant outside years 0001..9999, or a division or remainder by a zero-length `duration`, is a catchable refuse. `<` `>` `<=` `>=` also order two **lists** of an orderable element type or two **tuples** with orderable elements, of any lengths — **element-wise lexicographic**: the first unequal pair decides, and a prefix orders first (`[1, 2] < [1, 2, 3]`, `(1, 2) < (1, 3)`, `(1,) < (1, 2)`). An orderable element is a number, a `scientific`, a string, a datetime, a duration, or a **nested list/tuple whose own elements are orderable**, so the rule recurses all the way down (`[[1], [2]] < [[1], [3]]`, `[[2]] < [[10]]` — element `2 < 10`, not a string compare). Comparisons **chain**: `a < b < c` is `(a < b) and (b < c)` — each adjacent pair compared, the results conjoined, with `b` evaluated only once. `==` / `!=` also compare two **tuples** of any lengths (tuples of different lengths are unequal, and so are two whose positions hold different types, as `(1,) == ("a",)`; equal tuples agree position by position, §6.8) and two **sets** (element-wise and **order-independent** — two sets are equal when they hold the same elements, and an `int`, a `real` and a `bool` of equal value are the same element, so `{1, 2.0} == {1.0, 2}` is `True`, §6.10). `<` `>` `<=` `>=` on two sets compare them **by inclusion** (§6.10), and so do they on a dict's keys or items view beside a set or another such view (§6.9). Two **dicts** also compare with `==` / `!=`, **order-independent** — equal when they hold the same key→value mapping regardless of insertion order (numbers of either class by value, so `{1: "a"} == {1.0: "a"}` is `True`, a value of another type is unequal, and `{} == {"a": 1}` is `False`, §6.9). **Infinity and NaN.** `inf` and `-inf` order beyond every other number. A NaN compares as IEEE 754 has it: `math.nan == math.nan` is `False` and every ordered comparison with one is `False`. DKE Python has one NaN, and a collection finds it by value, where Python finds a NaN only when it is the very object: `float("nan") in [math.nan]` is `True`, `{math.nan: 1}[float("nan")]` is `1`, `len({math.nan, -math.nan})` is `1`, and `[float("nan")] == [float("nan")]` is `True` (Python: `False`, `KeyError`, `2`, `False`). Sorting puts a NaN after `inf`, so `sorted([3.0, math.nan, 1.0])` is `[1.0, 3.0, nan]` (Python: `[3.0, nan, 1.0]`), and `min` and `max` answer `nan` when any operand is one, as IEEE 754's `minimum` and `maximum` do ([Python Specification §8.1](/spec/dke/python/#comparison)). **Identity: `is` / `is not`.** `a is b` is `True` when `a` and `b` are one list, set or dict. A collection is shared (§7.17), so a name bound to another's list, a parameter, an element read and a loop variable each hold the collection itself, while a copy, a conversion, a comprehension and a literal each build a new one: after `b = a`, `a is b` is `True` and `a is a.copy()` is `False`. `is not` is the negation. Two collections of different kinds or element types are never one, so `[1] is {1}` and `[1] is ["a"]` are `False`, as in Python. Over a number or a string there is nothing to ask: whether two equal numbers or strings are the same object in Python depends on how the implementation interns them, which the language leaves undefined, so `is` there is a compile error that names `==` or `!=`. Over any other value, a tuple or a class instance, the error names the three kinds `is` takes. `x is None` and `x is not None` have no `==` rewrite either, since there is no `None` here — a rule asks whether a field has no value with `absent(...)` (§8.8), and over `None` the error names no comparison at all. The word itself is reserved, as it is in Python, so `is = 3` is a parse error. ### 6.1a Membership `in` / `not in` over `(string, string) → bool` — substring containment: `sub in s` is `True` when `sub` occurs anywhere in `s` (the empty string is in every string) — or element containment: `x in xs` over a constructed `list` (§6.7), `x in s` over a `set` (§6.10), `k in d` over a `dict` (testing its **keys**, §6.9) and `k in d.keys()` the same way, `x in d.values()` and `(k, v) in d.items()` over a dict's views (§6.9), and `x in t` over a `tuple`, each `True` when an element `==` the item. A number is found among numbers of either class by value, as `1 == 1.0` compares: `1 in [1.0]` is `True`, and so is `2.5 in (1, 2.5)`. A dict finds a numeric key the same way (§6.9). An item of another type is not found — `"a" in [1, 2]` is `False` — and a record tuple is searched for any of its fields: `"alice" in ("alice", 30)` is `True`. Membership does **not** test a store-derived collection — to iterate one, use the `for in :` statement (§7), whose `in` is the loop keyword, not this operator. ``` if "@" in address: ... if 4 in [3, 1, 4]: ... if "red" in tags: ... # set membership if "ana" in ages: ... # dict key membership ``` ### 6.2 Logical `and`, `or`, `not` over any value **that has a truth value** — `False` / `0` / `0.0` / `0j` / `""` / empty constructed collections are falsy, everything else truthy. **The eight store-derived read-result types have none** — `active_claim`, `claim_list`, `proof_tree`, `diagnosis`, `subject_set`, `string_list`, `verify_result`, `result_type_set` — and are refused in all four condition positions: an `if` / `elif`, a comprehension `if` filter, the condition of `a if c else b`, and an operand of `and` / `or` / `not`. This diverges from Python on purpose. An empty store read and a read that found nothing to tell you are different answers, and one `False` would say both; use `.length` to ask about emptiness and `for … in` to walk. `and` / `or` are **short-circuit** and **operand-return** — `a and b` is `a` when `a` is falsy else `b`; `a or b` is `a` when `a` is truthy else `b` — so `name or "anon"` returns the string. Their result type joins the operands (identical → that type; `int`/`real` → `real`; a `complex` and a number → `complex`; two other value scalars → `value`), and the operand returned keeps its class, so `1 or 2.5` is `1`; a non-unifiable pair is a type_error. `not` always yields a `bool`. `not` binds looser than comparison, so `not a == b` is `not (a == b)`. ### 6.2a Conditional expression (ternary) `a if c else b` yields `a` when `c` is **truthy** (any value that has a truth value, §6.2), else `b`. The two branches must share a type, which is the result type, be two class instances, which join under their common base as a list's elements do, be a scalar beside a `value` or two scalars that are not both numbers, which join to a `value` holding their classes, or be two numbers: an `int`/`real` pair is typed `real`, and the branch selected is returned unconverted, so `2.5 if False else 1` is `1`. A `complex` and a number join the same way, to `complex`: `2j if False else 1` is `1`. Evaluation is **short-circuit** — only the selected branch runs, so an untaken branch that would refuse is never reached (`safe if d == 0 else 1 / d`). A name holding a `value` that the condition tests is read in the first branch at what the test holding leaves it, and in the second at what its failing leaves (§8.11). It binds lower than every operator and is right-associative. ``` grade = "pass" if score >= 60 else "fail" ``` ### 6.3 Arithmetic `+ - * / // % **` over numeric operands (`int`, `real` or `bool` — a `bool` computes as the `int` it is, so `True + True` is `2`); prefix `-` negates an `int` (`-True` is `-1`), and prefix `+` takes the same operands and changes no number (`+True` is `1`). The numeric operators **promote**: `int × int` stays `int`, and any `real` operand yields a `real`, converting the `int`. Nothing but arithmetic and `float(x)` converts: a value given to a declared `real`, and a selection of two numbers, keep the class they have. So a `real` field written with `1 + 0.0` holds `1.0`, and written with a `-> real` script's `return 1` holds `1`; the two are equal. One exception and one detail: - **`/` is true division** — it always yields a `real`, so `int / int` promotes (`7 / 2` is `3.5`, `8 / 2` is `4.0`). The floored integer quotient is **`//`** (`7 // 2` is `3`, `-7 // 2` is `-4` — floor rounds toward −∞). - **`%` and `//` are the floor pair** — both take the sign of the divisor (`-7 % 2` is `1`, `-7.5 % 2` is `0.5`), so `a == (a // b) * b + (a % b)` holds at either operand class. A remainder of zero takes the divisor's sign too, which only a real can show: `6.0 % -2` is `-0.0`. **Complex arithmetic.** `+`, `-`, `*`, `/` and `**` take a `complex` beside another `complex` or beside a number and answer a `complex`, what CPython answers for the same operands: `(3 + 4j) * 2` is `(6+8j)`, `(3 + 4j) / 2` is `(1.5+2j)`, `1 / (3 + 4j)` is `(0.12-0.16j)`, `1j ** 2` is `(-1+0j)`. The prefix `-` negates both parts, so `-4j` renders `(-0-4j)`. `//` and `%` do not take a `complex`, and neither do `divmod`, `round`, `int` and `float`; read `.real` or `.imag` for a part. A division by zero with a finite dividend refuses at run time in Python's words, `complex division by zero`. A part may be infinite or a NaN, and computes as *Infinity and NaN* (below) says. `**` is **power**, right-associative, binding tighter than the prefix `-` (`-2 ** 2` is `-4`, `2 ** 3 ** 2` is `512`): `int ** non-negative int` is an `int` (`2 ** 10` is `1024`); a `real` operand yields a `real`; an `int` base with a **negative** exponent is a `real` when the exponent is **written as a literal** (`2 ** -1` is `0.5`), and otherwise refuses at run time with a teaching hint — write the base as a `real` (`2.0 ** n`), or the exponent as a literal. Reals compute to IEEE-754-double precision and render in canonical decimal form. A power with a `real` operand and a `complex`'s `abs` are **correctly rounded**, and so is each power, logarithm, exponential and trigonometric step behind `**` over a `complex`, `geomean` and `entropy`: the answer is the same wherever it is computed. Python takes these functions from its platform, which rounds them correctly almost always; where it does not, Python's last digit differs from DKE's ([Python Specification §8.3](/spec/dke/python/#arithmetic)). Division / modulo by zero of finite operands and integer overflow **refuse at runtime**; a real result past the range is `inf` or `-inf`. Two `**` shapes refuse for reasons that are *not* overflow: a zero base under a negative exponent (`0 ** -1`) is a division by zero and says so, and a negative base under a fractional exponent is a complex number, which the `real` a power of two numbers is typed as cannot hold. Two spellings compute it. Written with both operands as literals the power is typed `complex`: `(-8) ** 0.5` is `(1.7319121124709868e-16+2.8284271247461903j)`, as in Python. And a `complex` operand makes any power a `complex`, so `complex(x) ** 0.5` answers for a negative `x`. A zero raised to a negative or a complex power refuses in Python's words, `0.0 to a negative or complex power`; a base that is not zero never does. A complex power's part past the range is `inf` or `-inf`, where Python raises `OverflowError: complex exponentiation`. A negative base under an integral exponent (`(-8.0) ** 3.0` is `-512.0`) and a zero base under a non-negative one (`0 ** 0` is `1`) are ordinary. **Infinity and NaN.** A `real`, and a part of a `complex`, may be `inf`, `-inf` or `nan`, and every operation takes one and answers IEEE 754's value, as Python's operators do: `math.inf - math.inf` is `nan`, `1 / math.inf` is `0.0`, and an operation with a NaN operand is `nan` (save `math.nan ** 0`, which is `1.0`). A result past the largest `real` from finite operands is `inf` or `-inf`, as `1e308 * 10` is in Python. A zero divisor refuses only when the operands are finite. DKE Python departs from Python where Python raises instead, and at three corners where Python's answer breaks its operator's rule: | Expression | DKE Python | Python 3.11 | |---|---|---| | `10.0 ** 400`, `round(1.7976931348623157e308, -308)` | `inf` | `OverflowError` | | `math.inf / 0.0`, `math.inf // 0.0`, `math.inf % 0.0`, `divmod(math.inf, 0.0)` | `inf`, `inf`, `nan`, `(inf, nan)` | `ZeroDivisionError` | | `math.inf // 1`, `-1 % math.inf`, `-1 // math.inf` | `inf`, `nan`, `-0.0` | `nan`, `inf`, `-1.0` | | `complex(1e200, 1e200) * complex(1e200, 1e200)`, `complex(1e308, 1e308) / complex(2, 2)` | `infj`, `(5e+307+0j)` | `(nan+infj)`, `(inf+0j)` | | `complex(1e200, 1e200) ** 2`, `complex(1e300, 0) ** 2` | `infj`, `(inf+0j)` | `(nan+nanj)`, `OverflowError` | | `complex(math.inf, 0) / 0j`, `complex(-1, 0) ** 1e308` | `(inf+nanj)`, `(nan+nanj)` | `ZeroDivisionError` | A part of a complex product, quotient or whole power of finite operands is its exact value rounded once, `inf` only where that value is past the range; Python loses some of them to an intermediate. A power whose phase is past the largest `real` answers as C99 Annex G says. Comparisons, collections, `min`, `max` and sorting treat a NaN as §6.1 says, and the `math` functions as §6.14 says ([Python Specification §8.3](/spec/dke/python/#arithmetic)). `*` also does **sequence repetition**: a `string`, a constructed `list` or a constructed `tuple` repeated by an `int` (either order) — `"ab" * 3` is `"ababab"`, `"-" * 20` a rule, `[0] * 4` is `[0, 0, 0, 0]`, `[1, 2] * 3` is `[1, 2, 1, 2, 1, 2]`, `(1, 2) * 2` is `(1, 2, 1, 2)`. A non-positive count yields the empty sequence (`"x" * 0` and `(1, 2) * 0` are `""` and `()`); the count must be an `int`. A `tuple`'s type is its position list, so its count must be **written as a literal** and the result may name at most 65536 positions; with a count the run supplies, repeat a `list` — whose length is not in its type — and convert: `tuple([1, 2] * n)`. One rule covers that and the negative literal exponent above, and the Specification states it once: a static type may depend on an operand's value only where the program writes that value as a literal. → [Python Specification §8.3](/spec/dke/python/#arithmetic) **Bitwise operators** — on two integers, a `bool` counting as the `int` it is, `&`, `|` and `^` are bitwise and, or and exclusive or, `<<` and `>>` shift left and right by a count of bits, and the prefix `~` inverts every bit, as in Python: `12 & 10` is `8`, `12 | 10` is `14`, `12 ^ 10` is `6`, `1 << 4` is `16`, `-5 >> 1` is `-3` and `~5` is `-6`. Two `bool`s given `&`, `|` or `^` answer a `bool` (`True ^ True` is `False`); `~` and the shifts answer an `int`. They bind between comparison and `+` / `-`, in Python's order — `|` loosest, then `^`, `&` and the shifts — so `i & 4 == 4` is `(i & 4) == 4`, and `~` binds as the prefix `-` does. `&=`, `|=`, `^=`, `<<=` and `>>=` assign the result. A negative shift count refuses in Python's words, `negative shift count` (`value_error`), and a left shift whose result leaves the `int` range refuses as any integer overflow does. `@` and `@=` read, of `*`'s level, and no value here answers them: each is a type error naming its operands, `unsupported operand type(s) for @: 'int' and 'int'`. → [Python Specification §8.3](/spec/dke/python/#arithmetic) **`abs(x)`** — the built-in absolute value: `abs(-5)` is `5`, `abs(-3.5)` is `3.5`; it keeps the argument's numeric type and takes exactly one number (a non-number, or a different argument count, is a type error). The `abs` of a `complex` is its magnitude, a `real`: `abs(3 + 4j)` is `5.0`. **`round(x[, n])`** — half-to-even ("banker's") rounding: `round(0.5)` and `round(2.5)` are `0` and `2`, `round(0.125, 2)` is `0.12`. The optional digit count `n` is an `int` (negatives allowed: `round(35, -1)` is `40`). Result type: `round(int, …)` → `int`; `round(real)` (no `n`) → `int`; `round(real, n)` → `real` (a real even at `n = 0`, so `round(2.5, 0)` is `2.0`). Rounding follows the stored double, so `round(2.675, 2)` is `2.67` (matching CPython). **`sum(iterable)` / `min(…)` / `max(…)`** — reduce an in-memory iterable, distinct from the same-named store-aggregate folds `sum(K.attr)` / … (the argument shape selects: a bare `K.attr` path → fold; anything else → this reduce). `sum` adds numeric elements (all-`int` → `int`, empty → `0`, any `real` → `real`, `complex` elements → `complex`, so `sum([1j, 2j])` is `3j`; over a list of both classes, §6.7, the class the additions give — `sum([1, 2.5])` is `3.5`). Where any element or the start is a `real` and none is a `complex`, the sum is the exact sum of the start and every element, rounded once: `sum([0.1] * 10)` is `1.0`, where Python 3.11 answers `0.9999999999999999`, and an `int` enters as the integer it is. It is refused as `arithmetic_overflow` only when that rounded value, or an all-`int` total, is out of range, and a zero sum is `-0.0` only when the start and every element are. A sum holding a `complex` adds left to right, as Python's does. A start value, `sum(xs, 10)` or `sum(xs, start=10)`, is added first, as in Python: `sum([1, 2], 10)` is `13`, an empty iterable answers it, and a `real` one makes the sum `real`. A start that is not a number takes what `+` adds to it: a list or tuple start concatenates (`sum([[1], [2]], [])` is `[1, 2]`, and a tuple sum has a runtime length), a `duration` start adds lengths and a `datetime` start is shifted by each `duration`. A start `+` cannot keep adding the elements to is refused, and so is a string start, as Python refuses it. `min`/`max` take one iterable **or** two-plus scalar values → the extreme in `sorted`'s order (numbers by value, a `scientific` by its magnitude, strings lexicographically, a datetime chronologically, a duration by length; the first of two equal elements); elements/values must share an orderable type (numeric, `scientific`, string, datetime, duration, or a nested list/tuple of those — the comparison recurses), and a `scientific` shares one with any number. An `int` and a `real` compare by value and the extreme keeps its class: `min([1, 2.5])` is `1`. `min`/ `max` of a runtime-empty iterable is a catchable refuse (`ValueError`), unless a keyword-only **`default=`** is supplied — `min([], default=0)` is `0` — which is returned only when the iterable is empty and whose type must match the element type (`min(, default=…)`; `default=` is invalid with multiple positional values). **`key=`** on `min`, `max` and `sorted` — compares the elements by what the named function returns for each: `max(words, key=len)` is the longest word, `sorted(words, key=len)` is shortest first. The name is a builtin taking one argument (`len`, `abs`, `ord`, `str`, …), a method of a built-in type (`key=str.lower`, over elements of that type), or a script — declared in the unit, imported by `from`, or `key=m.f` — called once per element, in order. Only the keys are compared, so only they must be orderable: `max([{"a": 1}, {}], key=len)` is `{'a': 1}`. Equal keys keep their order: `min`/`max` return the first of them, and `sorted` is stable both ways, so `sorted(["bb", "a", "cc"], key=len, reverse=True)` is `['bb', 'cc', 'a']`. `key=` goes with `default=` or `reverse=` in either order. It takes a name and nothing else — a `lambda`, a method of a value such as `d.get`, a call or an expression is a parse_error (write a `def` that computes the key and name it), `key=None` is too (leave `key=` out), and `sum` and the store fold `min(K.attr)` take no `key=`. DKE Python has no `lambda` because a lambda is a function held as a value, and no type holds a function. **`divmod(a, b)`** — the floored quotient and the remainder as one pair, equal to `(a // b, a % b)`: `divmod(7, 2)` is `(3, 1)` and `divmod(-7, 2)` is `(-4, 1)` (the quotient floors toward negative infinity, the remainder takes the divisor's sign, and `q * b + r == a` always holds). Both operands must be numeric, and the pair promotes together — `tuple` for two `int`s, `tuple` when either is `real`, never a mixed pair: `divmod(7.5, 2)` is `(3.0, 1.5)`. An element of a list of both classes (§6.7) gives a pair whose one class is the element's: `divmod([1, 2.5][1], 2)` is `(1.0, 0.5)`. A zero divisor, and the most-negative `int` over `-1`, are catchable refuses. **`reversed(xs)`** — an `iterator` (§6.12) over the elements in the reverse of the order `for` walks them, so a `dict` contributes its keys, a `set` its sorted elements and a dict view what it views, backwards: `"".join(reversed("abc"))` is `"cba"`, and `list(reversed(xs))` is a reversed copy. An iterator is not an argument, as in CPython, since an iterator reads only forwards. **`sorted(xs[, key=][, reverse=])` / `any(xs)` / `all(xs)`** — `sorted` returns a **new** `list` in ascending order (descending with `reverse=True`; a stable sort, `key=` and `reverse=` keyword-only, a key as above); without a key the elements must be orderable — numeric, `scientific`, string, datetime, duration, or a nested list/tuple of those (the sort recurses lexicographically, so `sorted([[10], [2], [1, 3]])` is `[[1, 3], [2], [10]]`). `int`s and `real`s sort by value and keep their classes (`sorted([3, 2.5])` is `[2.5, 3]`). A keyword `sorted` or a list's `sort` does not take is a type error in Python's words, named for `sort()` as Python names it (`'x' is an invalid keyword argument for sort()`). `any`/`all` fold **truthiness** (§6.2) to a `bool` — `any([])` is `False`, `all([])` is `True`. **`format(value, spec)` / `repr(value)`** — `format` renders a value per the Python format mini-language, with the empty spec if it is given none (`format(3.14159, ".2f")` → `"3.14"`, `format(42, "05")` → `"00042"`, `format(255, "x")` → `"ff"`, `format(1234.5, ".2")` → `"1.2e+03"`, `format(65, "c")` → `"A"`): `[[fill]align][sign][z][#][0][width] [grouping][.precision][type]`. The fill is any one character and align is `<`, `>`, `^` or `=`, which pads between a number's sign and its digits (`format(-5, "*=6")` → `"-****5"`). An `int` or `bool` takes `d x X o b c n` and every type a `real` takes; a `real` takes `e E f F g G n %`, a `string` `s`. A precision counts decimals under `e`, `f` and `%`, significant digits under `g`, `n` and with no type, and characters on a string; an `int` takes none. A `bool` formats as its `int` once any spec is written. `+` and a space write a positive number's sign (`format(5, "+")` → `"+5"`), `z` drops a negative zero's, `#` writes a prefix or keeps a point (`format(5, "#b")` → `"0b101"`), and `,` and `_` group digits (`format(1234567, ",")` → `"1,234,567"`), each as in Python. A type the value does not take is a catchable refuse; so are `=`, a sign, `z` or `#` on a string, and `c` of a number that is no character's code point. A collection (a list, tuple, dict, set, a dict's views, a slice) takes no format spec: a non-empty one is refused wherever it is written, in `format`, an f-string, `str.format` and `__format__` called by name, and an empty one answers `str` of the value. A `complex` formats as in Python: `format(3 + 4j, ".2f")` is `"3.00+4.00j"`, a precision alone counts significant digits in each part (`format(3.14159 + 2j, ".3")` is `"(3.14+2j)"`), it takes `e E f F g G n`, it is right-aligned by default, and zero padding or `=` is a catchable refuse. `repr(x)` gives the repr text (a string keeps its quotes, and a character Python would not print, such as a no-break space, is escaped: `repr("a\xa0b")` is `"'a\\xa0b'"`). Both power f-string `{v:spec}` / `{v!r}` (§6.4a). **`ord(s)` / `chr(i)`** — the Unicode codepoint round-trip. `ord(s)` returns the integer codepoint of a **one-character** `string` (`ord("A")` is `65`, `ord("æ")` is `230`); a string of any other length is a catchable refuse. `chr(i)` returns the one-character `string` for a codepoint `i` in `range(0x110000)` (`chr(97)` is `"a"`, `chr(937)` is `"Ω"`); an `i` outside that range — or a lone surrogate (`U+D800`..`U+DFFF`), which has no UTF-8 encoding — is a catchable refuse. **`bin(n)` / `hex(n)` / `oct(n)` / `ascii(x)`** — text for a value, as Python writes it. `bin`, `hex` and `oct` write an `int` in base 2, 16 or 8 behind `0b`, `0x` or `0o`, lowercase and unpadded: `hex(255)` is `"0xff"`, `oct(0)` is `"0o0"`, and a negative number is signed, `bin(-5)` being `"-0b101"`. `int(s, base)` reads each back. `ascii(x)` is `repr(x)` with every character past U+007F escaped, so `ascii("é")` is `"'\\xe9'"`; with nothing to escape it equals `repr`. Each takes exactly one argument, and `bin`, `hex` and `oct` an `int` or a `bool`. **`pow(base, exp[, mod])`** — with two arguments, `base ** exp` itself: `pow(2, 10)` is `1024` and `pow(2, -1)` is `0.5`, and it refuses as `**` does. With a third, modular exponentiation over `int`s, computed below the modulus so any exponent finishes: `pow(2, 10, 1000)` is `24`, `pow(3, -1, 7)` is `5` (a negative exponent raises the base's inverse), and the answer takes the modulus's sign, so `pow(2, 3, -5)` is `-2`. A zero modulus, and a base with no inverse, are catchable refuses (`value_error`). A non-integer in the three-argument form is a type error in Python's words: a `real` is refused, `pow() 3rd argument not allowed unless all arguments are integers`, a `complex` `complex modulo`, and anything else names the three classes, `unsupported operand type(s) for ** or pow(): 'int', 'str', 'int'`. **Type conversions.** `str(x)` renders any value to text (total, the `+`/`print` rendering); `int(x)` and `float(x)` convert a `string` / `int` / `real` / `bool`, or an element of a list of both number classes (§6.7), to an `int` / `real`. `int(3.9)` truncates toward zero (`3`), `int(" -7 ")` is `-7`, and `_` separates digits as it does in a literal (`int("1_000")` is `1000`, `float("1_000.5")` is `1000.5`); a string that is not a valid number (`int("abc")`, `int("3.5")`) or an out-of-range value is a catchable refuse (Python `ValueError`). A decimal digit of any script reads as its value and a Unicode space pads, as in Python: `int("٥5")` is `55`. With no argument each conversion is the zero of its type, as in Python: `int()` is `0`, `float()` is `0.0`, `str()` is `""` and `bool()` is `False`. **`int(s, base)`** parses a `string` in an explicit radix: `base` is `2`..`36` (or `0` to auto-detect from a `0x`/`0o`/`0b` prefix), a `0x`/`0o`/`0b` prefix is accepted when it matches `base`, an optional leading sign and `_` digit separators are allowed, and a digit outside the base, an out-of-range `base`, or a base-`0` decimal with a leading zero refuses (`int("ff", 16)` is `255`, `int("0b101", 2)` is `5`, `int("z", 36)` is `35`). An explicit base with a non-`string` value is a type error, as in Python. **`complex(re, im)`** builds a `complex` from two numbers, each keeping the sign of a zero, and **`complex(x)`** builds one from a single number, from a `complex`, or from a `string` holding the text a complex prints as (`complex("3+4j")`, `complex("-2.5j")`); with no argument it is `0j`. Either argument of the two-argument form may itself be a `complex`, computed as in Python, so `complex(1j, 2j)` prints `(-2+1j)`. A string that is not a complex is a catchable refuse (Python `ValueError`); a part spelled `inf` or `nan`, or past the range, is what `float` reads, as in Python: `complex("inf")` is `(inf+0j)`. **Arguments by name**: an argument Python lets a caller name may be named here too, in any order after the positional ones — `int(s, base=16)`, `round(number=x, ndigits=2)`, `complex(real=1, imag=2)`, `pow(base=2, exp=10, mod=1000)`, `str(object=x)` and `enumerate(iterable=xs, start=1)`. Each argument is evaluated where it is written, whichever position it fills, so `pow(exp=xs.pop(), base=xs.pop())` pops the exponent first, as in Python. An omitted `real` is `0`, so `complex(imag=2)` is `2j`. The names given to `dict` are keys, each the string written in any language: `dict(a=1)` is `{"a": 1}`, and `dict({"a": 1}, a=2, b=3)` writes them over a positional argument's entries, giving `{"a": 2, "b": 3}`. `int`'s text is positional only, as in Python, and a built-in function that takes no argument by name refuses one in Python's words and says what to do instead: `abs(x=1)` is a type error beginning `abs() takes no keyword arguments`. A method's names are listed with the method (§6.6, §6.9). **Collection conversions**: `list(x)` collects a finite collection's elements (a dict view what it views, a `string` its characters, a `tuple` its elements — `list((1,2,3))` → `[1, 2, 3]`, the element type what a `for` over the tuple binds, §6.8) into a `list`; `set(x)` collects them into a deduped (`1` and `1.0` once), sorted `set` (elements must be hashable — a scalar or a tuple of hashables); `dict(x)` builds a `dict` from another dict — a new dict holding its entries — or from any other collection of pairs, read as `update` reads one: a `list>`, a tuple of them, an `iterator>` or a `dict_items` — `dict(zip(keys, vals))`; `tuple(x)` collects the elements into a **variadic homogeneous tuple** `tuple` (runtime length, indexed by a runtime `int`, unpacked as a list is), and accepts a fixed `tuple` too — which is how a fixed tuple is widened to a variadic one. Called with no argument, each gives the empty collection, as in Python: `list()` → `[]`, `dict()` → `{}`, `tuple()` → `()`, `set()` → `set()`. `bool(x)` returns the **truthiness** (§6.2) of any value as a `bool` (`bool([])` → `False`, `bool("x")` → `True`). ### 6.4 Concatenation `+` joins **two `string`s** into a new string. A `string` + a **non-string** operand is a **type error** (as in Python, which raises `TypeError`) — convert the non-string side with `str(…)` (`"count = " + str(xs.length)`); there is **no** implicit string coercion. An f-string applies `str(…)` to each field for you (§6.4a). With **two `list` operands** `+` **concatenates** into a new list: `[1, 2] + [3, 4]` is `[1, 2, 3, 4]`. `xs += ys`, `xs.append(v)` and `xs.extend(ys)` change `xs` in place to the same concatenation (§7.17), and `xs += c` takes any collection, as `extend` does: `xs = [1]` then `xs += (2, 3)` leaves `[1, 2, 3]`. The result element type follows the list-literal rule (§6.7): same type → `list`, differing scalars → `list` (`[1] + [3.5]`, holding numbers), an empty operand takes the other side (`[] + [1]`); a non-scalar mismatch is a type error. A `list` plus a non-`list`, non-`string` operand is a type error. With **two `tuple` operands** `+` **concatenates** into a new immutable tuple: `(1, 2) + (3,)` is `(1, 2, 3)`. A tuple is heterogeneous, so the result keeps each operand's positions and their types rather than settling on one element type — `tuple + tuple` is `tuple`, and a constant index into the result reads the position it names (after `t = (1, "a") + (5,)`, `t[2]` is an `int`). The empty tuple takes the other side (`() + (1,)`). A `tuple` plus a non-`tuple` operand is a type error. When one side is a tuple whose length is only known at run time, the result is too, so it needs a single element type covering both sides — the same unification `list` concatenation uses, and element types that do not unify are a type error. ``` print("count = " + str(xs.length)) print("items = " + str(xs)) combined = xs + [99] # concatenate two lists pair = (1, "a") + (True,) # concatenate two tuples ``` **Either quote** — a string opens with `"` or `'` and ends at the next of the same, so `'a'` and `"a"` are one string and the other quote is ordinary content: `"it's"`, `'say "hi"'`. A prefix `u` or `U` changes nothing. A printed collection of strings, `['a', 'b']`, reads back as written. **Adjacent literals** — two or more string literals separated only by whitespace are joined at compile time, with **no** `+`, so a long constant can span lines without a run-time concatenation: `"one " "two"` is `"one two"` and `"a" "b" "c"` is `"abc"`. An **f-string** joins the run too, in either order and any mixture (`f"a" "b"`, `"a" f"{x}"`, `f"{x}" f"{y}"`), which is the idiomatic way to write a long interpolated message across lines — and the whole run is one interpolated string, costing what the single f-string it replaces costs. A typed value (`datetime("…")`, `duration("…")`, `blob("…")`) does **not** join: concatenation is for strings. Render one with `str(…)` and use `+`, or interpolate it. **Triple-quoted literals** — a string delimited by `"""` runs until the next `"""`, so it may span multiple lines (each newline is a literal `\n`) and hold lone `"`/`""` characters without escaping: `"""line one` … `line two"""`. The escapes are the same as a string in one quote and the value is an ordinary `string`. `'''` is the same with the other quote, and an f-string may be triple-quoted too. **Raw strings** — an `r` or `R` prefix makes a backslash an ordinary character: it stays in the value with the character after it, so `r"a\n"` is three characters and `r"C:\path"` is the path as written. A backslash still keeps the next `"` from ending the literal, so a raw string cannot end in one backslash. A raw string may be triple-quoted, joins an adjacent run like any string, and combines with `f` as `rf"…"` or `fr"…"`, where a brace still opens a field ([Python Specification §3.5.1](/spec/dke/python/#string-literals)). **Escapes** — a backslash inside a string begins an escape: `\\` a backslash, `\"` a double quote, `\'` a single quote, `\n` line feed, `\t` tab, `\r` carriage return, `\a` bell, `\b` backspace, `\f` form feed, `\v` vertical tab. Three take hex digits and are written into the value as UTF-8: `\xNN` is a codepoint up to `U+00FF` from exactly two digits — so `"\x85"` is the same one-character string as `"\u0085"`, as in CPython — `\uNNNN` one up to `U+FFFF`, and `\UNNNNNNNN` a codepoint up to `U+10FFFF` — the only escape that reaches above the BMP. One more names its codepoint rather than numbering it — `\N{name}`, below. One to three octal digits name a codepoint, so `"\101"` is `"A"`, and a backslash at the end of a line continues the string on the next line. Anything else after a backslash stays in the value with the backslash, as in CPython: `"\d"` is two characters, the same string as `"\\d"`. Two values are refused rather than encoded: a surrogate (`U+D800`..`U+DFFF`, written either way), and **any escape that would produce a NUL** — `\0`, `\x00` and `\u0000` alike — so a `string` never carries an interior NUL. **Named-character escape** — `\N{name}` is the codepoint with that Unicode character name, matched case-insensitively, across **every** script: `"\N{BULLET}"` is `"•"`, `"\N{GREEK SMALL LETTER ALPHA}"` is `"α"`, and `"\N{CJK UNIFIED IDEOGRAPH-4E00}"` is `"一"`. A name Unicode does not assign is a parse error. ### 6.4a Formatted strings (f-strings) An **f-string** — a string literal with an `f` (or `F`) prefix — writes a value into text directly: each `{ … }` field holds an expression that is evaluated and written in place. `f"a{x}b"` has the same value as `"a" + str(x) + "b"`, so a field is rendered the way `+` renders that value (§6.4) and an f-string is always a `string` (even a lone field: `f"{n}"` is `"42"`, not the `int` `42`). A field holds any expression — a bound variable, arithmetic, a method call, an index or slice; a store read is bound to a name first (§7.2), as with `+`. Write `{{` or `}}` for a literal brace. A field may carry a **conversion** and/or a **format spec**: `{x!r}` uses `repr(x)`, `{x!s}` uses `str(x)`; `{x:spec}` applies the format mini-language — `f"{x:.2f}"`, `f"{n:>5}"`, `f"{s:*^8}"` — and they combine (`{x!r:>10}`). A field may end in a **self-documenting `=`**: `f"{x=}"` writes the field's source text up to the `=` (whitespace preserved) followed by the value, defaulting to `repr` — `f"{x=}"` is `"x=5"`, `f"{ x = }"` is `" x = 5"`, `f"{s=}"` is `"s='hi'"` — and a `:spec`/`!conv` overrides that default (`f"{x=:.2f}"` is `"x=5.00"`). A trailing `==`/`>=`/`<=`/`!=` is a comparison, not the debug `=`. A spec may hold fields of its own, each replaced by its value before the spec applies: `f"{x:>{w}}"` pads `x` to the width `w` holds. A field may hold a string in the quote that does not enclose the f-string, as `f"{d['k']}"` does. Python's two older ways to format a string read as Python's do: `"{}".format(x)` and `"{a}".format_map(d)` (§6.6), and `"%.2f" % x`. A `%` with a string on its left takes the next element of a tuple on its right, or the one value that is not, and `%(key)d` looks its key up in a dict or a list. It knows `s` `r` `a` `d` `i` `u` `o` `x` `X` `e` `E` `f` `F` `g` `G` `c`, the flags, a width, a precision, `*` and `%%`. A fault in either refuses as the program runs, in Python's words. ``` print(f"count = {xs.length}") name = "Merlin" print(f"hi {name}, first char {name[0]}, upper {name.upper()}") ``` ### 6.5 Field access, indexing, and slicing `x.field` (see §11) composes on any **computed** value — a method result, an index, or a slice — not only a bare name (`text.upper().length`, `xs[0].value`, `hist[-1].source`); a store read is still bound to a name first (§7.2). The builtin `len(x)` is an alias for `x.length` (a `string`'s character count, a `blob`'s byte count, or a collection's element count). `xs[i]` (zero-based; index is `int`; out-of-range refuses). A `string` may also be indexed to read a single character (`s[i]` → a one-character `string`; a **negative** index counts from the end, so `s[-1]` is the last character) and **sliced** for a substring — `s[a:b]`, with either bound optional (`s[a:]`, `s[:b]`, `s[:]`) and negative bounds counting from the end (`s[-3:]`, `s[:-1]`), plus an optional **step** `s[a:b:step]` (a negative step reverses, so `s[::-1]` is `s` reversed and `s[::2]` is every other character). Slicing is total: it never refuses; bounds are clamped and an inverted range yields `""` — the sole exception is a step of `0`, a catchable refuse. A constructed `list` indexes and slices by the same rules (`xs[i]`, negative from-end permitted; `xs[a:b:c]` yields a new `list`); from-end (negative) positions apply to a `string` and a constructed list, while a **store-derived** collection index is non-negative. Positions count characters (a string) or elements (a list). A **`tuple`** slices too — `t[a:b]` yields a `tuple`, the sub-tuple of the selected positions when its bounds are constant integers, and a variadic tuple of what a computed index reads when one is computed (§6.8); `list(t[a:b])` converts the result to a list. An index of another type than `int` is refused at compile, in Python's words over a string, a list or a tuple: `list indices must be integers or slices, not str` for `[1]["a"]`. A **`blob`** indexes, slices and joins as Python's `bytes` do, over its bytes: `b[i]` is the byte at `i` as an `int` from 0 to 255, a negative `i` counting from the end; `b[a:b:c]` is the blob of the bytes selected, by the rules of a string's slice, a negative step walking from the high end and a step of `0` a catchable refusal; and `b1 + b2` is the blob of the bytes of `b1` and then those of `b2`. A position past either end refuses with `index out of range` (`index_out_of_bounds`), and an index of another type than `int` is refused at compile, `byte indices must be integers or slices, not str`. Each is one operation, however long the blob, and builds no cells ([Python Specification §3.5.8](/spec/dke/python/#blob-literals)). ``` b = blob("000102030405FF") print(b[1:4]) # 010203 print(b[::-2]) # FF040200 print(b[0], b[-1]) # 0 255 print(b[:2] + b[5:]) # 000105FF ``` **A slice is also a value.** `slice(stop)` or `slice(start, stop[, step])` builds one. Each argument is an `int`, a `bool` or `None`, and `None` or a bound left out is open, so `slice(5)` is `slice(None, 5, None)`, which is also how it prints. `xs[s]` selects what `xs[a:b:c]` with the same bounds selects, from a string, a list or a tuple, a fixed-length tuple reading it as a computed bound (§6.8). On a list, `xs[s] = c`, `xs[s] op= c` and `del xs[s]` change what the bracketed slice would. `s.start`, `s.stop` and `s.step` read the bounds back as `value`, an open one being the `null` class, which prints `null` where CPython prints `None`. `s.indices(n)` answers the `(start, stop, step)` that `xs[s]` selects from a sequence of length `n`, refusing a negative `n` (`value_error`) before a zero step (`slice_step_zero`). Two slices are `==` when their bounds are. A slice has no order and no `.length`, is always true, is not hashable (so it is no `set` element and no `dict` key), and is never stored. ### 6.6 String methods A `string` carries these built-in methods (all character-counting, all total except `split`, the justify family, and `index`/`rindex`, noted below): - `s.find(sub[, start[, end]])` → `int` / `s.rfind(...)` → `int` — the **first** / **last** position of `sub`, or `-1`; the optional `start[, end]` window searches `s[start:end]` and the index is into the whole `s`. - `s.index(sub[, start[, end]])` → `int` / `s.rindex(...)` → `int` — like `find` / `rfind`, but a missing `sub` **refuses** (catchable, §10) instead of returning `-1`. - `s.count(sub[, start[, end]])` → `int` — the number of non-overlapping occurrences of `sub` in the window (an empty `sub` yields the window's `length + 1`). - `s.startswith(prefix[, start[, end]])` → `bool` / `s.endswith(suffix[, ...])` → `bool` — whether the window of `s` begins / ends with the argument (an empty argument yields `True`). The argument may instead be a **tuple of strings** — `True` if the window matches **any** of them (an empty tuple yields `False`). - `s.replace(old, new[, count])` → `string` — a copy of `s` with each non-overlapping occurrence of `old` replaced by `new`, at most `count` times when given (an empty `old` inserts `new` in every gap, as in CPython: `"abc".replace("", "-")` → `-a-b-c-`). - `s.upper()` → `string` / `s.lower()` → `string` — the full Unicode case mapping. A character may expand to more than one (`"straße".upper()` is `"STRASSE"`, `"file".upper()` is `"FILE"`, `"İ".lower()` is `"i̇"`), so a case transform can change the length (which `.length` reflects). `lower()` also applies the Greek word-final rule (`"ΟΔΟΣ".lower()` is `"οδος"`, final `ς`). - `s.capitalize()` → `string` — first character titlecased, the rest lowercased. - `s.title()` → `string` — the first character of each word titlecased, the rest lowercased (a word is a run of cased characters; any non-letter, including an apostrophe, starts a new word — so `"they're".title()` is `"They'Re"`). - `s.swapcase()` → `string` — each uppercase character lowercased and vice versa. (These four case transforms use the full Unicode mapping above, including the Greek word-final rule where they lowercase.) - `s.casefold()` → `string` — aggressive, **context-free** folding for caseless matching: like `lower` but more so (`"STRAßE".casefold()` is `"strasse"`, `"fi"` folds to `"fi"`), and it does **not** apply the Greek word-final rule, so `"ΟΔΟΣ".casefold()` is `"οδοσ"` (a medial `σ`, unlike `.lower()`'s `"οδος"`). Use `a.casefold() == b.casefold()` for caseless equality. - `s.zfill(width)` → `string` — left-pad with `'0'` to `width` characters, keeping a leading `+`/`-` sign first; `width ≤ s.length` is a no-op. - `s.ljust(width[, fill])` / `s.rjust(width[, fill])` / `s.center(width[, fill])` → `string` — justify `s` in a field of `width` characters, padding right / left / both with `fill` (a single character, default space). `width ≤ s.length` is a no-op; a `fill` that is not exactly one character **refuses** (catchable). - `s.strip([chars])` / `s.lstrip([chars])` / `s.rstrip([chars])` → `string` — `s` with characters removed from both ends / the left / the right. With no argument they remove whitespace; with a `chars` argument they remove any character that occurs in the `chars` set. - `s.removeprefix(prefix)` / `s.removesuffix(suffix)` → `string` — `s` with a leading `prefix` / trailing `suffix` removed if present, else `s` unchanged (an empty `suffix` is a no-op). - `s.expandtabs([tabsize])` → `string` — each tab replaced by spaces to the next tab stop (multiple of `tabsize`, default `8`); the column counts characters and resets after `\n`/`\r`. `tabsize ≤ 0` removes tabs. `tabsize` may be named: `s.expandtabs(tabsize=4)`. - `s.split([sep[, maxsplit]])` → `list` — the substrings of `s` split at each occurrence of `sep` (empty pieces kept); an optional `maxsplit` caps the splits (≤ `maxsplit + 1` pieces). With **no** argument, `s` splits on runs of whitespace, dropping empty pieces. An explicit empty separator is a catchable refuse (§10). Both arguments may be named, `s.split(sep=",", maxsplit=1)`, and naming `maxsplit` alone bounds a whitespace split, keeping the rest as one piece: `" a b c ".split(maxsplit=1)` is `['a', 'b c ']`. - `s.rsplit([sep[, maxsplit]])` → `list` — like `split`, but a bounded `maxsplit` takes its splits from the **right** (`"a,b,c".rsplit(",", 1)` is `['a,b', 'c']`); with no `maxsplit` the result equals `split`. It takes the same names, and `" a b c ".rsplit(maxsplit=1)` is `[' a b', 'c']`. - `sep.join(xs)` → `string` — the strings `xs` yields, concatenated with `sep` between adjacent ones: `xs` is a `string` (its characters), or a `list`, `tuple`, `set`, `dict` (its keys) or iterator of strings, and an empty one yields `""`. `"-".join("abc")` is `"a-b-c"`. Elements of any other type are a type error, in Python's words: `"a".join([1])` is refused with `sequence item 0: expected str instance, int found`, and `"a".join(1)` with `can only join an iterable`. - `s.partition(sep)` / `s.rpartition(sep)` → `tuple` — split `s` **once** at the first / last occurrence of `sep` into `(before, sep, after)`; when `sep` is absent, `partition` yields `(s, "", "")` and `rpartition` yields `("", "", s)`. An explicit empty separator is a catchable refuse (§10). - `s.translate(table)` → `string` — `s` with each character whose code point `table` holds replaced by what it holds for it: an `int` or `bool` is a code point, a `string` stands as written, and any other character stays. A dict from code points, as `str.maketrans` makes one (§11), finds a code point as any dict does, so `97.0` is the key `97`. A list, a tuple or a string is indexed by it, and a character past its end stays. A table of any other type, a value of any other type, a code point outside `range(0x110000)` or a surrogate is a catchable refuse (§10), when a character reaches it. - `s.format(*args, **kwargs)` → `string` — `s` read as a template, as Python reads it: each `{}` takes the next positional argument, `{0}` the one at that position and `{a}` the keyword. After that, `.x` reads a field and `[w]` an item, `!r`, `!s` and `!a` convert, and `:spec` is what `format(value, spec)` takes (§6.4a). `{{` and `}}` are a brace. `.x` on an instance refuses as the program runs, since the store is not read in the middle of an expression: bind the field to a name and pass that. A fault refuses in Python's words: `key_not_found` for a name no argument holds, `index_out_of_bounds` for a position past the last, `type_error` or `value_error` for the rest. - `s.format_map(m)` → `string` — as `format`, every name looked up in `m`. - `s.splitlines([keepends])` → `list` — the lines of `s`, split at each of CPython's eleven line boundaries: `\n`, `\r\n`, `\r`, the vertical tab, the form feed, the three C0 separators, U+0085, U+2028 and U+2029 — with the trailing-newline empty line dropped (`""` yields `[]`). With a true `keepends`, given by position or by name, each line keeps the boundary that ends it: `"a\r\nb".splitlines(keepends=True)` is `['a\r\n', 'b']`. - **Arguments by name**: the names above are the only ones a method takes, as in Python, besides a list's `sort` (`key=`, `reverse=`) and a dict's `update`, whose names are keys. Any other method refuses a name in Python's words, naming the receiver's Python class: `s.center(width=9)` is a type error beginning `str.center() takes no keyword arguments`. - **Arguments of a type a method does not take** are a type error in Python's words, which differ by method as CPython's do: `"abc".find(1)` is refused with `must be str, not int`, `"abc".replace("a", 1)` with `replace() argument 2 must be str, not int`, `"abc".split(1)` with `must be str or None, not int` and `"abc".zfill("a")` with `'str' object cannot be interpreted as an integer`. A search's bounds are a slice's: `"abc".find("a", 2.5)` is refused with `slice indices must be integers or None or have an __index__ method`, and a list's or a tuple's `index` bounds with `slice indices must be integers or have an __index__ method`. A list's `insert` position is an integer: `xs.insert("a", 2)` is refused with `'str' object cannot be interpreted as an integer`. An argument of a type Python has no class for, such as a claim's `value`, is told which type the method takes. - The character-class predicates — each `() → bool`, testing every character of `s` against its Unicode class: `s.isspace()`, `s.isdigit()`, `s.isdecimal()`, `s.isnumeric()`, `s.isalpha()`, `s.isalnum()`, `s.isupper()`, `s.islower()`, `s.istitle()`, `s.isascii()`, `s.isprintable()`, `s.isidentifier()`. All but `isascii` and `isprintable` are `False` on the empty string (the whole-string rule is "non-empty and all characters match"); `"".isascii()` and `"".isprintable()` are `True`. `isupper`/`islower` require at least one cased character and no character of the opposite case; `istitle` tests the titlecase pattern. They classify by full Unicode, so `"blåbær".islower()` and `"ÆØÅ".isalpha()` are `True`. `isprintable` is `False` for a control, a format character, any separator but the space, and a private-use or unassigned codepoint. `isidentifier` asks what Python asks of a name, and reserves no keyword: `"class".isidentifier()` is `True`. ``` domain = email.strip()[email.find("@") + 1:] label = raw.replace("_", " ").upper() paid = invoice.endswith("PAID") tidy = title.strip("*_ ") parts = "a,b,c".split(",") # ['a', 'b', 'c'] line = " ".join(["a", "b", "c"]) # "a b c" addr = "user@host".partition("@") # ('user', '@', 'host') rows = "one\ntwo".splitlines() # ['one', 'two'] digits = code.isdigit() # True if code is all digits at = path.index("/") # position of "/", or refuses if absent ``` ### 6.7 Constructed lists and comprehensions A **list literal** `[e1, e2, …]` builds a constructed `list` (shared, bounded, existing only while the program runs and never stored). Elements are inferred to a common type: homogeneous → `list`, mixed scalars → `list`, empty `[]` → `list`; lists nest. The empty list is compatible with any `list` — wherever two list types must agree it takes the concrete side's type: `[1, 2] if c else []`, a re-binding (`xs = []` then `xs = [1]`), an annotated binding (`xs: list = []`, and `d: dict = {}` likewise), a nested sibling (`[[1, 2], []]` is `list>`), `",".join([])`, and `[] == xs`. The empty set `set([])` and the empty dict `{}` are compatible the same way with any set and any dict, so the accumulator `seen = set([])` then `seen |= {w}` binds a `set` when `w` is a string. A constructed list is indexed, sliced, measured (`.length`), iterated (`for … in`), tested for membership (`x in xs`), searched (`xs.index(v[, start[, end]])` → the first index of `v`, within an optional codepoint window and refusing if absent; `xs.count(v)` → how many times `v` occurs), compared (`==` / `!=`) and copied (`xs.copy()` → a new list holding the same elements); it renders in `print` and `+`. A list is **shared**: every name holding it holds the one list, and a statement that changes it — `xs.append(v)`, `xs[i] = v`, `del xs[i]` and the rest of §7.17 — changes it in place, so every one of them sees the change. A list mixing only `int`s and `real`s — `[1, 2.5]` — holds **numbers**. Its type is still `list` and each element keeps its own class (`[1, 2.5][0]` is `1`), but arithmetic, ordering, membership, `sum` / `min` / `max` / `sorted`, `abs` / `round` / `divmod` and `int` / `float` take its elements as numbers: `min([1, 2.5])` is `1`, `sum([1, 2.5])` is `3.5` and `[1, 2.5][1] + 1` is `3.5`, as in Python. So do a set, a dict's values and a tuple's positions that mix the two classes, and whatever is read from them — an index, a loop or comprehension variable, a generator expression, `+` of two such lists. A result is a `real` where a `real` decides it (`[1, 2.5][0] + 0.5`, and `/`), and otherwise again a `value` holding a number, which satisfies no `int` or `real` annotation or parameter: convert it with `int(…)` or `float(…)`. Anything that could bring a non-number in removes the property — a conditional expression or `or` choosing between `[1, 2.5]` and `["a", 1]`, a re-binding, a nested literal. A `bool` is a number here, the `int` it is, as in Python: `sum([True, 2])` is `3`, `True in [1, 2.5]` is `True`, and `max([True, 0])` is `True`, the element itself. It is an `int` by class too, as in Python: `[True, 2]` is a `list`, a parameter, annotation or return declared `int` takes a `bool`, so do an index, a bound and a repetition count, and a `case int` arm over a `value` takes `True` — write `case bool` above it to tell the two apart. It keeps its class, so `x: int = True` prints `True`. A `list` is still not a `list`, since a list is shared. [Python Specification §6.4](/spec/dke/python/#assignment-type-agreement) and [§8.1](/spec/dke/python/#comparison) state the rule. A list mixing a `scientific` with numbers — `[scientific("2.0e0"), 1]` — holds **magnitudes**: its elements order by magnitude, as a `scientific` does (§6.1), so `sorted`, `min` and `max` take it and `sorted([scientific("2.0e0"), 1, 2.5])` is `[1, 2.0e0, 2.5]`. They compute nothing, so arithmetic and `sum` refuse them. The same holds for a set, a dict's keys or values and a tuple's positions, for whatever is read from them, and for `min` or `max` of a `scientific` and a number. [Python Specification §5.6](/spec/dke/python/#the-value-union) states the rule. A **comprehension** `[expr for x in xs if cond]` builds a new list by bounded iteration: it binds each element of the finite source `xs` to `x`, keeps those the optional `cond` (truth-tested) admits, and collects `expr`. The loop variable is comprehension-local (it may shadow an outer name and does not leak). Over an empty source whose element type nothing supplied, a comprehension is empty, and what reads `x` is not checked (§7.7): `[x.upper() for x in []]` is `list`. A clause may name a **target list** of two or more names, or one name and a trailing comma (`[k for k, v in d.items()]`), which destructures each element the way a `for` statement does (§7.7): the element must be a tuple of that arity or a list. **Nested** targets (`[c for a, (b, c) in xs]`) and one optional **starred** target (`[r for a, *r in xs]`) are taken here as they are everywhere else, the star making the arity a minimum. A clause's targets are names — an element target goes in a `for` statement, which has a body to write it in. **Multiple `for` clauses** form the flat cartesian product (`[a + b for a in xs for b in ys]`) — the first clause is the outermost loop, and a later clause's source may reference earlier loop variables (`[y for row in grid for y in row]`); a clause may carry several `if` filters (applied conjunctively, each asked only for its truth value). A comprehension whose result is itself a comprehension also nests. ``` nums = [3, 1, 4, 1, 5] evens = [n for n in nums if n % 2 == 0] # [4] caps = [w.upper() for w in "a,b".split(",")] # ['A', 'B'] grid = [[x for x in nums] for r in caps] # list> pairs = [a + b for a in [1, 2] for b in [10, 20]] # [11, 21, 12, 22] flat = [y for row in [[1, 2], [3, 4]] for y in row] # [1, 2, 3, 4] ``` A **generator expression** is a comprehension written in a call's parentheses instead of brackets, `sum(x for x in xs)`. It is accepted as the whole argument of a call that reads every element before it returns: `sum`, `min`, `max`, `any`, `all`, `sorted`, `list`, `tuple`, `set`, `dict`, a string's `join`, a set's `union`, `intersection`, `difference`, `symmetric_difference`, `issubset`, `issuperset` and `isdisjoint`, a list's `extend`, a set's `update`, `difference_update`, `intersection_update` and `symmetric_difference_update`, and a dict's `update`. There it means the bracketed comprehension, except that `any` and `all` stop at the element that decides, and a set's `update` and `difference_update` and a dict's `update` read it one element at a time (§7.17), as Python's do. Beside another argument it is written in its own parentheses, `sorted((x for x in xs), reverse=True)`. Written bare beside another argument, in a call of any kind, it is a grammar error at its start, as in Python, so `print(1, x for x in xs)` is refused at column 10. In its own parentheses right after a `*`, in a call or a display, it is read the same way, since the `*` reads every element first: `print(*(x for x in xs))`, `[*(x for x in xs)]`. Anywhere else it would be a generator object, which DKE Python does not have: bound to a name, returned, printed, or passed to `len`, `enumerate`, `zip`, a method not listed here or a script, it is refused with a message naming `[… for … in …]`. ``` nums = [3, 1, 4, 1, 5] total = sum(n * n for n in nums) # 52 big = any(n > 3 for n in nums) # True ordered = sorted((n % 3 for n in nums), reverse=True) # [2, 1, 1, 1, 0] ``` ### 6.8 Tuples A **tuple literal** `(e1, e2, …)` builds a constructed `tuple` — a fixed-length, **heterogeneous** sequence in which each position keeps its own type (immutable, bounded, never stored). A comma makes the tuple: `(e)` is a grouped expression, `(e,)` a one-element tuple, `(a, b)` a two-element tuple, `()` the empty tuple; tuples nest (`((1, 2), 3)` is a `tuple, int>`). After `return` and on the right of an assignment the parentheses may be left off, as in CPython: `return 1, 2` returns `(1, 2)`, and `x = 1,` binds `(1,)`. A tuple indexed by a **compile-time constant** integer reads that position's own type — `t[0]` the first position's, `t[-1]` the last's. A computed index reads what a `for` over the tuple binds, below, and is a compile-time error only over a tuple holding a collection beside a value of another type (every position stays reachable by a literal index); an index past either end refuses when it runs, catchably, as Python's `IndexError` does. It is measured with `.length` and compared with `==` / `!=` against any value — `(1, 2) == (1,)` is `False`, and so is `(1,) == ("a",)`. A tuple is written after `for … in` when its positions share one type, are all scalars, which the loop variable takes as a `value` (§6.7, §7.7), or are all class instances, which it takes at their common base (§8.10); a tuple holding a collection beside a value of another type has no single element type to bind, so index it instead. It renders in `print` and `+` as `(e1, e2, …)`, a one-element tuple as `(e,)`. Two tuples **concatenate** with `+` (§6.4), the result keeping every position's own type. A tuple is **searched** (`t.index(v[, start[, end]])` → the first position of `v`, within an optional window and refusing if absent; `t.count(v)` → how many positions hold `v`) wherever `v in t` is allowed, comparing `v` with every position as `==` does — `(1, "a").index("a")` is `1`, and a missing value refuses as `tuple.index(x): x not in tuple`. A fixed tuple whose positions all agree with `T` **is** a `tuple`, a tuple being immutable, so it fills one wherever that variadic type is declared — as a value, inside a collection literal written at the declaration (`ts: list> = [(1, 2)]`), by an element write (`ds[k] = (1, 2)`), and by `append` / `insert` / `extend`. A collection a **name** already holds is not widened into a second name, a list being shared. ``` point = (3, 4) # tuple x = point[0] # 3 — constant index at = point.index(4) # 1 mixed = (1, "ana", True) # tuple same = (3, 4) == point # True wide = point + (5,) # tuple ``` ### 6.9 Dicts A **dict literal** `{k1: v1, k2: v2}` builds a constructed `dict` — a mapping from **hashable** keys (a scalar or a tuple of hashables) to values, kept in **insertion order** (shared as a list is, bounded, never stored). Keys are drawn from a scalar value class (`string` / `int` / `bool` / `datetime` / `duration` / `real` / `blob` / `scientific` / `complex`), a `value`, or a tuple of hashables, and are homogeneous (a mix of scalar value classes gives a `value`-typed key part, and any other mix, such as a tuple beside a string, is a type error naming the two types, in a literal or written by `d[k] = v`, since a `value` holds a scalar); values join as a list's elements do: a mix of scalar value classes gives a `value`-typed value part, holding numbers when all are `int`s and `real`s (§6.7), collections whose elements meet are one type (`{"a": [1], "b": []}` is a `dict>`), and instances of several subclasses take their common base (§8.10). The empty dict is `{}`. When a key is written twice the entry keeps its **first position** but takes the **last value** (`{"x": 1, "x": 9}` is `{'x': 9}`). A key matches by value, so an `int` and a `real` of equal value are one key, as in Python — `{1: "a", 1.0: "b"}` is `{1: 'b'}`, and a `bool` and the `int` it is are one key too, `{True: "a", 1: "b"}` is `{True: 'b'}` — and the keys of a dict of `int` and `real` keys hold numbers in the §6.7 sense (`sum({1: "a", 2.5: "b"})` is `3.5`). A `**d` entry gives every entry `d` holds at that place, so `{**d, "a": 0}` keeps `0` for `"a"`, and `d | e` is the same merge as `{**d, **e}`. A list, a set or a dict cannot be a key: writing one is told `unhashable type: 'list'`, and so is looking one up, as the program runs, whatever the dict holds (`[1] in d`, `d.get([1], 0)`). A dict is: - **read by key** — `d[k]` returns `V`; the key is compared with the keys as `==` does (a number finds a key of either class, `{1: "a"}[1.0]` is `'a'`), and a key absent at run time, or of another type, is a catchable **refuse** (§10), never a silent default; - **tested** — `k in d` / `k not in d` over its **keys** (§6.1a); - **measured** — `.length`, its entry count; - **walked** — `for k in d:` binds each **key** in insertion order (§7.7); - **viewed** — `d.keys()` → `dict_keys`, `d.values()` → `dict_values`, and `d.items()` → `dict_items`, each a view that reads the dict in insertion order as it stands whenever the view is read (below); - **read defensively** — `d.get(key, default)` returns the value for `key`, or `default` when the key is absent. A default is **required** (there is no `None` for a 1-argument form); the result type is the join of `V` and the default's type; - **copied** — `d.copy()` or `dict(d)` → a new dict holding the same entries; - **changed** — `d[k] = v`, `d[k] += v`, `del d[k]`, `d.update(e)` and `d.clear()` are statements that change `d` in place (§7.17). `update` also takes names, each written as a string key: `d.update(a=1)` sets `d["a"]`, and `d.update(e, a=1)` applies `e` first. It renders in `print` and `+` as `{k1: v1, k2: v2}` in insertion order. Two dicts compare with `==` / `!=` **order-independently** — equal when they hold the same key→value mapping regardless of insertion order (`{"a": 1, "b": 2} == {"b": 2, "a": 1}` is `True`); its key and value types must meet, and an empty dict unifies with any dict. ``` ages = {"ana": 30, "bo": 25} ages["ana"] # 30 "bo" in ages # True names = ages.keys() # dict_keys(['ana', 'bo']) pairs = ages.items() # dict_items([('ana', 30), ('bo', 25)]) ``` **Views.** A view follows its dict, as Python's does: after `names = ages.keys()` and `ages["cy"] = 41`, `names` holds `"cy"`. It is walked by `for` and a comprehension, read by the conversions (`list(v)`, `set(v)`, `dict(ages.items())`) and by every builtin that reads a collection, has a length (`len`, `.length`) and a truth value, and takes `in`. It has no position: `names[0]` is a type_error in Python's words, `'dict_keys' object is not subscriptable`, naming `list(...)`. A keys or items view is set-like — it equals a set holding its elements, takes `&` `|` `-` `^` beside any collection on either side, as Python's does, and `<` `<=` `>` `>=` beside a set or another such view, answering a set: `{"a": 1}.keys() & ["a"]` is `{'a'}`. A number beside a view is a type_error in Python's words, `'int' object is not iterable`. Of the set methods it has `isdisjoint` alone. A values view equals only itself. A view prints as Python's does and is not a key or set element. An annotation writes one `dict_keys`, `dict_values` or `dict_items`. ### 6.10 Sets A **set literal** `{a, b, c}` (no colons) builds a constructed `set` — a collection of **unique** hashable elements — a scalar, a `value`, or a tuple of hashables (shared as a list is, bounded, never stored). A `{ … }` with a colon is a dict (§6.9), so — because the empty braces `{}` are the empty dict — the **empty set has no literal**: write `set()`, as in Python, or `set([])`; either renders `set()`. Elements are homogeneous (a mix of scalar value classes gives a `set`, holding numbers when all are `int`s, `real`s and `bool`s, §6.7, and any other mix, such as `{1, (1, 2)}`, is a type error naming the two types, as a list's is); duplicates are removed at construction, and an `int`, a `real` and a `bool` of equal value are duplicates, as in Python, the first written kept (`{1.0, 1}` is `{1.0}`, `{True, 1}` is `{True}`). A set renders and is walked (`for x in s:`) in a **deterministic sorted order** — a defined refinement of Python, whose set order is unspecified. It is the order `sorted(s)` gives: numbers rank by value, an `int` and a `real` together, so `{2.5, 1, 3}` renders `{1, 2.5, 3}` and a loop over `{2, 1.5}` meets `1.5` first. A set is: - **tested** — `x in s` / `x not in s` (§6.1a); - **measured** — `.length`; - **combined** — `s | t` (union), `s & t` (intersection), `s - t` (difference) and `s ^ t` (symmetric difference), or the methods `s.union(t)`, `s.intersection(t)`, `s.difference(t)` and `s.symmetric_difference(t)`, each returning a new sorted set, where `union`, `intersection` and `difference` take any number of collections and with none answer a copy of `s`; - **compared** — `==` / `!=`, element-wise and order-independent; `s <= t` (subset), `s < t` (proper subset), `s >= t` (superset) and `s > t` (proper superset); and the methods `s.issubset(t)`, `s.issuperset(t)` and `s.isdisjoint(t)`; - **copied** — `s.copy()` → a new set holding the same elements; - **changed** — `s.add(x)`, `s.discard(x)`, `s.remove(x)`, `s.update(t)`, `s.difference_update(t)`, `s.intersection_update(t)`, `s.symmetric_difference_update(t)` and `s.clear()` are statements that change `s` in place (§7.17). `update`, `difference_update` and `intersection_update` take any number of collections, and with none change nothing. An operator takes two sets, or a set and a dict's keys or items view, which also takes any collection (§6.9). A method takes any collection `set(t)` would — a set, list, tuple, string, dict (its keys) or generator expression — as Python's do. An `int`, a `real` and a `bool` of equal value are one element to every relation (`{1} <= {1.0}` is `True`). A relation that only compares takes any element type, since an element of another type equals nothing: `{1} <= {"a"}` and `{1}.issubset(["a"])` are `False`, `{1}.isdisjoint(["a"])` is `True`, and `{1} - {"a"}`, `{1} & {"a"}` and `.difference` and `.intersection` keep `s`'s element type. `|`, `^`, `.union` and `.symmetric_difference` keep elements of both, joined as a list literal's are: a set of `int`s joined with a set of `string`s is a `set`, as Python builds a mixed set, and one joined with a set of tuples is a type error, since a `value` holds no tuple. Joined in place, by `|=`, `^=`, `.update` or `.add`, a `set` that would hold a `string` is a type error, as a name keeps its type. On two integers `|`, `&` and `^` are bitwise (§6.3), and `|` on two dicts merges them (§6.9), `d |= e` changing `d` in place, `e` being anything `update` reads, so `d |= [("b", 2)]` too. On anything else but sets they are type errors. `s |= t`, `s &= t`, `s -= t` and `s ^= t` **change** `s` in place to the new set, as `xs += ys` changes a list, so another name holding the set sees the change, as in Python (§7.17). Inclusion is a **partial** order (`{1} < {2}` and `{2} < {1}` are both `False`), so a list of sets is not sortable, and `min` and `max` do not take sets. It renders in `print` and `+` as `{e1, e2, …}` in sorted order. ``` tags = {"red", "green", "red", "blue"} # {'blue', 'green', 'red'} (unique, sorted) "red" in tags # True warm = {"red", "orange"} shared = tags.intersection(warm) # {'red'} both = tags.union(warm) # {'blue', 'green', 'orange', 'red'} cool = tags - warm # {'blue', 'green'} inside = shared <= warm # True apart = warm.isdisjoint(["blue", "gray"]) # True ``` ### 6.11 Dict and set comprehensions Alongside the list comprehension (§6.7), a **set comprehension** `{expr for x in xs if cond}` builds a new `set` — deduping and sorting, with `expr` hashable (a scalar or a tuple of hashables) — and a **dict comprehension** `{key: value for x in xs if cond}` builds a new `dict` in insertion order, last value winning on a repeated key and `key` hashable on the same terms. Both iterate a finite source, bind each element to the comprehension-local `x` (or to a target list, §6.7), keep those the optional `cond` admits, and collect the result — the same bounded iteration as a list comprehension, so both terminate and store nothing. A brace form with a `:` in its head is a dict comprehension; without one, a set comprehension. ``` squares = {n: n * n for n in [1, 2, 3]} # {1: 1, 2: 4, 3: 9} parity = {n % 2 for n in [1, 2, 3, 4]} # {0, 1} (deduped, sorted) ``` ### 6.12 zip, enumerate, iter, next and range Two builtin functions pair finite collections into tuples, each returning an **iterator**, as CPython's do: - `zip(a, b, …)` → `iterator>` — pairs elements position by position, up to the **shortest** input (`zip(["a", "b"], [1, 2, 3])` gives two tuples), and `zip()` gives none. `zip(a, b, strict=True)` pairs them the same way and refuses, in CPython's words, if the inputs differ in length: `zip() argument 2 is shorter than argument 1`. The refusal is a `value_error`, and the pairs read before it have been given. - `enumerate(xs [, start])` → `iterator>` — numbers each element, from `0` by default or from an optional `int` `start` (positional or the `start=` keyword; may be negative). Each argument is any finite collection (a `dict` contributes its keys, a `set` its sorted elements, a dict view what it views) or an iterator. `reversed` (§6.3) returns one too. `iter(x)` → `iterator` makes one over any finite collection, read as `for` reads it, and answers an iterator with itself, so `iter(e)` is `e`. It prints as CPython's class for it: `` over a list, ``, ``, `` over a dict, and over a string ``, or `` when a character is not ASCII. It takes exactly one argument; CPython's two-argument form calls a function, and no value here is one. An iterator is not a key or set element, where CPython hashes one by its identity: a key here is held by its value, and an iterator is equal only to itself ([Python Specification §5.1](/spec/dke/python/#the-base-types)). `any`, `all`, `in`, a set's `intersection`, `issuperset` and `isdisjoint` stop reading an iterator once the answer is known, as CPython's do, and leave the rest. `next(e [, default])` reads the next element of the iterator `e`. When none is left, `next(e, default)` answers `default`, which is evaluated before the read, and `next(e)` is a `refuse` with discriminator `stop_iteration` where CPython raises `StopIteration`. The default and the elements join as two elements of one list display do (§6.7), so a default of another scalar class gives a `value` (§4.1), and one that cannot join, such as a list beside an `int`, is a type_error. `next(xs)` over a list is a type_error in CPython's words, `'list' object is not an iterator`. `next` takes no keyword argument, and `next(e)` may stand alone as a statement, which skips an element (§7.4). An iterator is **read once**. `for`, a comprehension, `list`, `tuple`, `set`, `dict`, `sorted`, `sum`, `min`, `max`, `any`, `all`, `join`, `extend`, `update`, `zip`, `enumerate`, `next` and unpacking each take the elements they read, so a second read finds only what the first left. A `for` reads one per pass, so `break` leaves the rest, and `x in e` reads up to the first match. Every name holding an iterator holds the same one. An iterator never reads past the length its source had when it was made; a dict or set that changes size refuses the next read in CPython's words, `dictionary changed size during iteration`. A set's `update` and `difference_update` and a dict's `update` read one element at a time, so one over the collection they change refuses as soon as its size changes (§7.17). It has **no length and no position**: `len(e)`, `e[0]` and `e[1:]` are type_errors that name `list(...)`, which reads it into a list with both. It is always truthy, equal only to itself, and prints as its class without CPython's address — ``, ``, ``. Printing it reads nothing. An annotation writes it `iterator`. ``` paired = zip(["ana", "bo"], [90, 85]) print(paired) # print(list(paired)) # [('ana', 90), ('bo', 85)] print(list(paired)) # [] print(list(enumerate(["ana", "bo"], 1))) # [(1, 'ana'), (2, 'bo')] e = iter([1, 2]) print(next(e), next(e)) # 1 2 print(next(e, "done")) # done for name, score in zip(names, scores): # tuple unpacking (§7.7) print(name + " scored " + str(score)) ``` `range(stop)`, `range(start, stop)` and `range(start, stop, step)` → `list`: the integers from `start` (default `0`) up to but not including `stop`, `step` apart (default `1`); a negative `step` counts down. It is how a counting loop gets its numbers. Every argument is an `int`, and there are one to three of them. A zero `step` is a `refuse` with CPython's text, `range() arg 3 must not be zero` (discriminator `value_error`). ``` range(4) # [0, 1, 2, 3] range(2, 7) # [2, 3, 4, 5, 6] range(10, 0, -3) # [10, 7, 4, 1] range(5, 2) # [] for i in range(3): print(i) ``` Unlike CPython's, a range is an ordinary `list`. It prints as its elements (`print(range(3))` prints `[0, 1, 2]`, where CPython prints `range(0, 3)`) and equals the list of them, and `reversed(range(3))` prints `` where CPython's is a `range_iterator`. It is built in full, so its elements count toward a service's budget on how much one run builds (§10), as `[0] * n`'s do. ### 6.13 Spreading a collection A `*` before an element of a list, a tuple or a set **spreads** a collection into it, as in Python: its elements stand in that place, in the order `for` walks them. ```python a = [1, 2] print([*a, 3]) # [1, 2, 3] print((0, *a)) # (0, 1, 2) print({*a, *{2, 4}}) # {1, 2, 4} print([*"ab", *{"k": 1}]) # ['a', 'b', 'k'] t = *a, 3 # (1, 2, 3) for x in *a, 3: # 1, 2, 3 print(x) ``` Anything `for` walks may be spread (§7.7): a list, a set, a dict (its keys), a dict view, a string, a tuple and an iterator, which the spread reads to its end. Anything else is a compile-time `type_error` that names the spread, where Python raises `Value after * must be an iterable, not int` as the program runs. The elements and spreads are evaluated left to right, each once, and the result is a new collection: `ys = [*xs]` is a copy, and a change to `ys` is not seen through `xs`. In a subscript a spread makes the index a tuple, as a comma does: `d[*k]` is `d[(*k,)]`. The type is the one the elements would give written out. A list is a `list` and a set a `set`, with `T` joined as a list literal's elements join (§6.7). A tuple whose spreads are all of tuples written with a fixed number of positions keeps every position's type, so `(0, *(1, "a"))` is a `tuple`. One that spreads anything else has no length in its type, so it is a `tuple`: `(*xs, 3)` with `xs` a `list` is a `tuple`. A starred value alone is no tuple: `y = *a`, `return *a` and `for x in *a:` are each a `parse_error` at the `*`, "can't use starred expression here", as in Python, and `y = *a,` is the tuple. As a comprehension's element, `[*x for x in xs]`, it is a `parse_error`, "iterable unpacking cannot be used in comprehension". A `*` before an argument of `print` spreads the collection across `print`'s arguments (§7.13); so does one before an argument of a script's call, §7.4, and one before a method's argument, §8.4; before an argument of a verb it is not taken. One before an argument of a builtin spreads the collection across the call's arguments, which then answers what it answers written out: `max(*xs)`, `zip(*rows)`, `range(*bounds)`. A count the builtin does not take is refused in Python's words: at compile for a tuple of fixed length or a list display, and as a `refuse` with discriminator `type_error` as the program runs for any other. A builtin whose result's type would depend on the count is a `type_error`, as `max(*xs)` is for a list of lists. A keyword argument beside the `*` binds as Python binds it, written before the `*` or after it: `int(*["ff"], base=16)`, `round(*xs, ndigits=1)`, `sorted(*xs, reverse=True)`, `max(*xs, default=0)`, `zip(*rows, strict=True)`. A count that does not fit the keyword is refused the same way, in Python's words, and a name the builtin does not take is a `type_error`. One before an argument of a built-in method spreads the same way, `"-".join(*[xs])`, `"abc".replace(*pair)`, and through a type's name what it gives first may be the receiver, `str.upper(*["a"])`. A count a built-in method does not take, spread or written out, is refused in Python's words, `str.upper() takes no arguments (1 given)`, `replace expected at least 2 arguments, got 1`: at compile where the count is written or fixed, and as a `refuse` with discriminator `type_error` as the program runs for any other. A name beside the `*` binds as written out, once the count is known: `"a b c".split(*ys, maxsplit=1)`, `d.update(*ys, k=1)`, `xs.sort(*ys, reverse=True)`, which sorts where `ys` gives nothing and otherwise raises `sort() takes no positional arguments`, in Python's words. One before an argument of a call into `math`, `cmath`, `vector` or `struct` spreads the collection across the call's arguments, as in a script's call: given `p = [1.0, 1.0]`, `math.atan2(*p)` answers what `math.atan2(1.0, 1.0)` answers. A count of arguments the function does not take is then refused as the program runs, with `type_error`, and `math.isclose` places an `a` or `b` given by name at the count the `*` gives: with `xs` holding one element, `math.isclose(*xs, b=1.0)` compares it with `1.0`. A `**` in a dict is not taken either; to merge two dicts, copy one and `update` it (§6.9). ### 6.14 `math` and `cmath` ``` import math import cmath ``` Python's `math` and `cmath`, provided by the language: an `import` of either reads nothing from your store (§9.4), and `math.` is available from the `import` to the end of the unit (§9.3). `from math import sqrt, pi` binds the names it lists, and leaves `math` itself unbound, as in Python. | Name | Answers | |------|---------| | `math.sqrt(x)` | the square root, a `real` | | `math.exp(x)` | e to the power `x` | | `math.log(x)`, `math.log(x, base)` | the natural logarithm; with `base`, the logarithm of `x` to that base, rounded once: `math.log(1000, 10)` is `3.0` | | `math.log10(x)`, `math.log2(x)` | the logarithm to base 10, and to base 2 | | `math.erf(x)`, `math.erfc(x)` | the error function, and its complement `1 - erf(x)` | | `math.gamma(x)`, `math.lgamma(x)` | the gamma function, and the natural logarithm of its magnitude: `math.gamma(5)` is `24.0` | | `math.sin(x)`, `math.cos(x)`, `math.tan(x)` | of an angle in radians | | `math.asin(x)`, `math.acos(x)`, `math.atan(x)` | the angle in radians whose sine, cosine or tangent is `x`: `asin` and `atan` from `-π/2` to `π/2`, `acos` from `0` to `π` | | `math.atan2(y, x)` | the angle of the point `(x, y)`, from `-π` to `π` | | `math.hypot(*coordinates)` | the distance from the origin to the point with these coordinates, any number of them | | `math.degrees(x)`, `math.radians(x)` | the angle `x` from radians to degrees, and from degrees to radians | | `math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)` | a `bool`: whether `a` and `b` are within `rel_tol` of the larger in magnitude, or within `abs_tol` | | `math.floor(x)`, `math.ceil(x)`, `math.trunc(x)` | an `int`: the greatest integer no larger than `x`, the least no smaller, and the one nearest zero | | `math.sinh(x)`, `math.cosh(x)`, `math.tanh(x)`, `math.asinh(x)`, `math.acosh(x)`, `math.atanh(x)` | the hyperbolic functions and their inverses | | `math.cbrt(x)`, `math.exp2(x)`, `math.expm1(x)`, `math.log1p(x)`, `math.pow(x, y)` | the cube root; 2 to the power `x`; e to the power `x` less 1; the natural logarithm of 1 plus `x`; and `x` to the power `y`, always a `real` | | `math.fabs(x)`, `math.copysign(x, y)`, `math.fmod(x, y)`, `math.remainder(x, y)`, `math.nextafter(x, y)`, `math.ulp(x)` | the magnitude; `x` with the sign of `y`; the remainder with the sign of `x`; the remainder from the nearest whole quotient; the next `real` after `x` towards `y`; the gap to the next `real` | | `math.modf(x)`, `math.frexp(x)`, `math.ldexp(x, i)` | `(fraction, whole)`, a `tuple`; `(mantissa, exponent)`, a `tuple`; and `x` times 2 to the power `i`, an `int` | | `math.gcd(*integers)`, `math.lcm(*integers)`, `math.comb(n, k)`, `math.perm(n[, k])`, `math.factorial(n)`, `math.isqrt(n)` | an `int`, of `int`s: `math.gcd()` is `0` and `math.lcm()` is `1`; `perm` with one argument takes `k` as `n`; a `real` argument is a `type_error` | | `math.fsum(iterable)`, `math.prod(iterable, *, start=1)`, `math.dist(p, q)` | the exact sum of the elements, a `real`; the product of `start` and the elements, an `int`, a `real` or a `complex`; and the distance between two points of one length, a `real` | | `math.pi`, `math.e`, `math.tau` | the nearest `real`s to π, e and 2π | | `math.inf`, `math.nan` | the positive infinity and the NaN | | `math.isinf(x)`, `math.isnan(x)`, `math.isfinite(x)` | a `bool`: whether `x` is an infinity, a NaN, or neither; an `int` and a `bool` are finite | | Name | Answers | |------|---------| | `cmath.exp(z)` | e to the power `z`, a `complex` | | `cmath.sqrt(z)` | the principal square root, a `complex` | | `cmath.phase(z)` | the argument of `z`, a `real` from `-π` to `π` | | `cmath.polar(z)` | `(abs(z), phase(z))`, a `tuple` | | `cmath.rect(r, phi)` | the `complex` of modulus `r` and argument `phi` | | `cmath.isinf(z)`, `cmath.isnan(z)`, `cmath.isfinite(z)` | a `bool`: whether a part of `z` is an infinity, whether a part is a NaN, and whether both parts are finite | | `cmath.pi`, `cmath.e`, `cmath.tau`, `cmath.inf`, `cmath.nan` | as in `math` | | `cmath.infj`, `cmath.nanj` | the `complex` with a zero real part and an imaginary part that is `inf`, and `nan` | A `math` function takes an `int`, a `real` or a `bool`; a `cmath` function takes a `complex` too, except `rect`, whose two arguments are real. Anything else is a `type_error`; `prod` takes a `complex` too. Only `isclose` and `prod` take keyword arguments, `prod`'s `start` by name only, and `hypot`, `gcd` and `lcm` take any number of arguments, none included. A wrong number of arguments is a `type_error` in Python's words, `math.sqrt() takes exactly one argument (2 given)` or `math.log requires 1 to 2 arguments`. A constant called (`math.pi()`), a function used as a value (`f = math.sqrt`), a name the namespace does not have, a namespace used without its `import`, and a call on a line of its own are each a `parse_error`. ``` import math import cmath print(math.sqrt(2)) # 1.4142135623730951 print(math.log(8, 2)) # 3.0 print(math.log(1000, 10)) # 3.0 print(math.erf(1)) # 0.8427007929497149 print(math.gamma(5)) # 24.0 print(math.atan2(1, 1) == math.pi / 4) # True print(math.degrees(math.asin(0.5))) # 30.000000000000004 print(math.hypot(3, 4, 12)) # 13.0 print(cmath.exp(1j * math.pi / 2)) # (6.123233995736766e-17+1j) print(cmath.sqrt(-4)) # 2j print(cmath.polar(3 + 4j)) # (5.0, 0.9272952180016122) ``` Each function follows Python's algorithm with every power, logarithm, exponential, trigonometric step, inverse and modulus correctly rounded, as `**` is (§6.3), so an answer is the same wherever it is computed and departs from Python's only where Python's platform rounds one of those steps wrongly ([Python Specification §8.16](/spec/dke/python/#the-math-and-cmath-namespaces)). `degrees` and `radians` are the exact product rounded once, where Python's rounds twice, so a last digit may differ from Python's: `math.degrees(-880.7976600675347)` is `-50465.9885268683`, where Python answers `-50465.98852686831`. `math.log(x, base)` is rounded once too, where Python's divides two rounded logarithms: `math.log(1000, 10)` is `3.0`, where Python answers `2.9999999999999996`. `hypot` of any number of coordinates is correctly rounded, and `dist` and `hypot` of one point never differ. `fsum`, `prod` and `dist` are the exact value rounded once, where Python's round at every step: `math.fsum([1e308, 1e308, -1e308])` is `1e+308`, where Python raises `OverflowError`; `math.prod([1e200, 1e200, 0.0])` is `0.0`, where Python answers `nan`; and `math.dist([-1.7, 1.6, -9.6], [2.3, 2.6, -8.8])` is `4.199999999999999`, where Python answers `4.2`. `prod` takes numbers, where Python's multiplies whatever `*` takes. `cmath.exp`, `cmath.rect` and `cmath.sqrt` give each part, real and imaginary, as its exact value rounded once, where Python's rounds the parts it composes and again: `cmath.exp(complex(1.3, 2.7)).imag` is `1.5681835703495033`, where Python answers `1.5681835703495035`. A call refuses where Python raises, in Python's words: an argument outside the domain (`math.sqrt(-1)`, `math.log(0)`, `math.asin(2)`, `math.atanh(1)`, `math.fmod(1, 0)`, or a pole of the gamma function, `math.gamma(0)`, `math.gamma(-1)`, `math.lgamma(-3)`) with `value_error`, `math domain error`; an `int` answer past what an `int` holds (`math.factorial(21)`) with `arithmetic_overflow`, ``integer overflow: `math.factorial` result exceeds the signed 64-bit range``; an infinity or a NaN to `math.floor`, `math.ceil` or `math.trunc` with `arithmetic_overflow`, `cannot convert float infinity to integer`, or `value_error`, `cannot convert float NaN to integer`; `math.log(x, 1)` with `division_by_zero`, `float division by zero`; and a negative tolerance to `isclose` with `value_error`, `tolerances must be non-negative`. A result too small to represent is the nearest one that is: `math.exp(-1000)` is `0.0`. Two departures from Python: a result past the largest `real` is `inf` or `-inf` (`math.exp(1000)`, `math.gamma(172)`, `math.fsum([1e308, 1e308])`, `cmath.exp(800)` is `(inf+0j)`), where Python raises `OverflowError`; and an infinite argument with no value is `nan` (`math.sin(math.inf)`, `math.sqrt(-math.inf)`, `math.fmod(math.inf, 2.0)`, `math.fsum([math.inf, -math.inf])`), where Python raises `ValueError`. There is one NaN, so `math.copysign(1.0, -math.nan)` is `1.0`, where Python answers `-1.0` ([Python Specification §8.16](/spec/dke/python/#the-math-and-cmath-namespaces)). A constant costs nothing, as the literal it names does; each call costs one operation. ### 6.15 `vector` ``` import vector ``` A namespace the language provides, imported and reserved as `math` is (§6.14). Python has no module of this name. Its two functions apply a matrix to a list, and add the squares of a list, along axes, each in one call. | Name | Answers | |------|---------| | `vector.apply(xs, m, axes)` | `xs` with the square matrix `m` applied along the axes `axes` | | `vector.sumsq(xs, axes)` | the sum of the squared parts of the elements of `xs`, for each pattern of the axes `axes` | `xs` is a list of 2ⁿ numbers, and axis `a` is bit `a` of an index into it, so axis 0 is the lowest bit. `axes` names k different axes, and `m` is 2ᵏ rows of 2ᵏ numbers. The first axis named is the high bit of a row or column of `m`, so along `[a, b]` row 2 of `m` is the element whose bit `a` is 1 and whose bit `b` is 0, and along `[1, 0]` the rows of a 4 × 4 matrix follow the order of a four-element list. For each setting of the bits the axes do not name, the 2ᵏ elements the named bits reach are replaced by `m` times them: the element at row `r` becomes `m[r][0] * x0 + m[r][1] * x1 + …`, where `xc` is the element at column `c`, added left to right. Each element keeps the class that arithmetic gives it, as in Python, and the answer is that of a Python function stated in full ([Python Specification §8.17](/spec/dke/python/#the-vector-namespace)). ``` import vector print(vector.apply([4.0, 8.0], [[0.75, 0.25], [0.25, 0.75]], [0])) # [5.0, 7.0] print(vector.apply([1, 2, 3, 4], [[1, 0], [1, 1]], [0])) # [1, 3, 3, 7] print(vector.apply([1, 2, 3, 4], [[1, 0], [1, 1]], [1])) # [1, 2, 4, 6] upto = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] print(vector.apply([10, 20, 30, 40], upto, [1, 0])) # [10, 30, 60, 100] print(vector.apply([10, 20, 30, 40], upto, [0, 1])) # [10, 60, 40, 100] print(vector.apply([1.0, 2.0], [[1, 0], [1j, 1]], [0])) # [1.0, (2+1j)] ``` An argument that is not a list of numbers, a matrix that is not a list of lists of numbers, and axes that are not a list of integers are each a `type_error`. A call refuses with `value_error` where the length of `xs` is not a power of two, an axis is outside 0 to n − 1 or named twice, or `m` is not 2ᵏ rows of 2ᵏ; and with `arithmetic_overflow` where a product or sum passes the range of its class, as `*` and `+` do (§6.3). A call costs one operation, whatever the length of `xs`. `vector.sumsq` takes the same `xs` and the same `axes`, and answers a list of 2ᵏ reals: one sum for each pattern of the bits the axes name, listed with the first axis named as the high bit of the pattern, as the rows of `m` are. The sum for a pattern is `math.fsum`'s over `z.real * z.real` and `z.imag * z.imag` of every element `z = complex(x)` whose bits on the axes make that pattern, so it is rounded once and does not depend on the order the elements are in. Naming no axis makes one sum of every element. The answer is that of a Python function stated in full ([Python Specification §8.17](/spec/dke/python/#the-vector-namespace)). ``` import vector print(vector.sumsq([3, 4], [])) # [25.0] print(vector.sumsq([1, 2, 3, 4], [0])) # [10.0, 20.0] print(vector.sumsq([1, 2, 3, 4], [1])) # [5.0, 25.0] print(vector.sumsq([1, 2, 3, 4], [1, 0])) # [1.0, 4.0, 9.0, 16.0] print(vector.sumsq([1, 2, 3, 4], [0, 1])) # [1.0, 9.0, 4.0, 16.0] print(vector.sumsq([1.0 + 2.0j, True, -0.0, 0.0], [1])) # [6.0, 0.0] ``` A vector that is not a list of numbers and axes that are not a list of integers are each a `type_error`. A call refuses with `value_error` where the length of `xs` is not a power of two or an axis is outside 0 to n − 1 or named twice. A square, or a sum of squares, past the range of a real is `inf`, where Python's `fsum` may raise `OverflowError`, and an element with a NaN part makes its sum `nan`: `vector.sumsq([1e200, 1.0], [0])` is `[inf, 1.0]`. A call costs one operation, whatever the length of `xs`, and builds the 2ᵏ sums alone. ### 6.16 `struct` ``` import struct ``` Python's module of that name, provided by the language, imported and reserved as `math` is (§6.14). It lays numbers out as the bytes of a blob, and reads them back, by a format string, as Python's does. | Name | Answers | |------|---------| | `struct.pack(format, v1, v2, …)` | a blob holding the values, laid out as the format describes | | `struct.unpack(format, buffer)` | a tuple of the values the format reads from the blob `buffer` | | `struct.calcsize(format)` | how many bytes the format lays out | A format opens with an optional `@` (native sizes, each value aligned, the default), `=` or `<` (little-endian, no padding), or `>` or `!` (big-endian), and then has codes, each after an optional count. The integers are `b`, `h`, `i`, `l` and `q`, of 1, 2, 4, 4 and 8 bytes, signed, with their unsigned twins `B`, `H`, `I`, `L` and `Q`; `l` and `L` are 8 bytes in native layout, and `n`, `N` and `P` are 8-byte integers that only native layout has. `?` is a `bool`, `e`, `f` and `d` are reals of 2, 4 and 8 bytes, and `F` and `D` are complex numbers of two `f` or two `d`. `x` is a pad byte, `c` a blob of one byte, and `s` and `p` one blob whose length is the count. Native layout is Python's on a 64-bit x86 Linux host, wherever a program runs, so a format lays out the same bytes everywhere. ``` import struct b = struct.pack("I", blob("0000FF01"))) # (65281,) zs = [1j, complex(-0.0, 2.0)] kept = struct.pack(f"<{len(zs)}D", *zs) print(list(struct.unpack(f"<{len(kept) // 16}D", kept))) # [1j, (-0+2j)] ``` `unpack` takes its tuple's types from a format written in the call: a string literal gives each position the type of its code, so `struct.unpack("`, and an f-string whose holes are integers and whose codes all read one type gives a tuple of that type, as `f"<{n}D"` gives a `tuple`. Any other format gives a `tuple`. A call refuses where Python raises, in Python's words: with `value_error` for a format it cannot read, a count of values or a length of blob the format does not lay out, a value its code does not pack, or an integer outside its code's range, and with `arithmetic_overflow` for a finite real too large for `e` or `f`. A DKE `int` is at most 2⁶³ − 1, so `unpack` refuses an unsigned integer above that with `arithmetic_overflow`. An infinity or a NaN packs and unpacks as in Python, and there is one NaN: `pack` writes it as the quiet NaN with its sign bit clear (`7ff8000000000000` for `d`), where Python writes the bits of the NaN it was given. A call costs one operation, however many values it packs or reads, and a `*` argument one more (§6.13). The rules in full are in [Python Specification §8.18](/spec/dke/python/#the-struct-namespace). ### 6.17 Special names on a built-in value A special name is one that begins and ends with two underscores. A value holds the special names its Python class holds, and one called by name answers as the spelling it stands for, at that spelling's cost. A reflected name takes its operands the other way round. ``` xs = [3, 1] print(xs.__len__()) # 2 print((2).__rsub__(10)) # 8 print(xs.__contains__(3)) # True xs.__setitem__(0, 5) # xs[0] = 5 xs.__iadd__([7]) # xs += [7] print(xs) # [5, 1, 7] ``` A name the class does not hold, a count of arguments its method does not take, and a keyword argument are type_errors in Python's words. So is `__hash__` on a list, a dict or a set, which is `None` in Python: `'NoneType' object is not callable`. Where Python answers something no value here holds, the call is a type_error saying so: `NotImplemented`, which an operator's special name answers for an operand its class does not take; `__class__`, a class; a special name read with no call, a method; and the machinery behind a value, such as `__sizeof__`. A special name that changes its receiver, `__setitem__`, `__delitem__` or an in-place name such as `__iadd__`, is a statement, as `append` is (§7.17), and read as a value it is a type_error. A `*` and a `**` before any dict are read as at any method (§7.4): the names are merged and refused in Python's words, `wrapper __len__() takes no keyword arguments`, before the count is, as the program runs. Beside a `**` whose keys are known only as the program runs, a call whose answer no value here holds, and a change read as a value, are refused as the program runs too, after the names, and a change is made first; a value bound from one is a `type_error` where the unit is compiled. The rules in full are in [Python Specification §8.20](/spec/dke/python/#special-names-on-a-built-in-value). --- ## 7. Statements Every statement is one logical line, or a `:`-headed block with an indented suite. A `;` ends a simple statement too, so `a = 1; b = 2;` is two, and a block's simple statements may stand on its header's line, as `if a: b = 1; c = 2` and `def f(): pass` do. A compound statement stands on a line of its own, and never after a `;`. Indent with **spaces only**. A line is indented deeper only as the first line of a block, below a header that ends in `:`. A line indented deeper anywhere else is a parse_error, "unexpected indent". **An expression alone is a statement**: it is evaluated and its value dropped, so `x + 1`, `xs[0]` and `s.upper()` run, and a type or a name the expression gets wrong is refused as it is in a binding. `...` alone does nothing, as `pass` does. An annotation with no value, `x: int`, binds nothing: a read of `x` needs a binding below it. A list written inside `( )`, `[ ]` or `{ }` may end with one **trailing comma**, and it changes nothing: the arguments of any call, the parameters of a `def` or a method, a class's base, and the elements of a literal or a pattern. `f(1, 2,)` is `f(1, 2)`, and a call written one argument per line may end every line with a comma. A comma never stands alone or doubled: `f(,)` and `[1,,2]` are refused. In a subscript, a comma makes the index a tuple, as in Python: `d[1, 2]` is `d[(1, 2)]`, and `d[1,]` is `d[(1,)]`. ### 7.1 Declaration ``` def audit(K: string, s: string): def doubled(n: int) -> int: # value script: returns an int return n * 2 ``` The `def` keyword, name, typed parameters (`name: type`), an optional **`-> type`** return type, `:`, indented body. A `type` is a scalar / read-result type (§5.5) **or** a parameterized collection — `list`, `set`, `dict`, `tuple`, nestable (`dict>`) — so a collection crosses a function boundary as an argument or return value (`def total(xs: list) -> int:`, `def doubled(xs: list) -> list:`). A parameter may carry a **default** (`def f(x: int, y: int = 1)`) — a constant expression whose type matches the parameter; a defaulted parameter may not precede a required one. Two declarations in a unit must not share a name, whatever their kinds: a `def`, a `@rule`, a `@constraint` and a `class` share one name space, and a second declaration of a name is refused with the kind that holds it. A call may target a declaration above or below it in the unit, as long as no call leads back to its caller (compile-before-call). A module's own statement runs where it stands, so one that reaches a `def` below it is told `name 'g' is not defined` when the unit compiles. A parameter may be **variadic** — written `*name: T` — collecting the surplus positional arguments into a `tuple` (possibly empty): `def total(label: string, *nums: int) -> int:` accepts `total("a", 1, 2, 3)` with `nums = (1, 2, 3)`. There may be at most one, its element type is required, it takes no default, and it cannot be passed by keyword; inside the body it is an ordinary tuple (`nums[0]`, `len(nums)`, `for n in nums`, `sum(nums)`). There is no `**kwargs`: a signature names every keyword a call may pass, so a misspelt keyword is refused where it is written. Use explicit named parameters or a `dict` parameter. A `/` makes the parameters before it **positional-only**, and a bare `*` or a variadic makes those after it **keyword-only**, as in Python: `def f(a: int, /, b: int, *, c: int = 3)` is called `f(1, 2)` or `f(1, b=2, c=4)`. Passing `a` by name or `c` by position is a `type_error` naming the parameter and its marker. A keyword-only parameter with no default may follow one with a default. A script with no `-> type` whose body holds no `return ` is a **void script** (no value); one written `def name(...) -> type:` is a **value script** — it produces a value of `type` and MUST `return` a value on every path (§7.12). With no `-> type`, a body holding a `return ` is a value script of the type its `return`s give. Only a value script may be called as an expression (§7.4). A value script that hands on a store fold's result declares `-> value`, as a value method does (§8.5), and its caller narrows it with `match`. ### 7.2 Assignment (bind a read) ``` hist = get(K.s.color) ``` A name is bound by bare assignment, `name = `. The right side must not be a statement-only verb (no value). **Augmented assignment** `x += e` (and `-=` `*=` `/=` `%=` `//=` `**=`) is accepted on a **bare name**; `x = e` is equivalent to `x = x e`. It does **not** apply to a field (`obj.field += e` is rejected): a field is a provenance-carrying claim, not a mutable cell, and a compound assignment names no source — update a field by reading it, then writing the new value back with `remember(obj.field, , )`. An **annotated assignment** `x: = ` gives the binding its type explicitly (`n: int = 5`, `xs: list = [1, 2, 3]`); the annotation must agree with the initializer, and a bare `x: ` with no value is rejected (write `x: = `). An **element write** `xs[i] = v` or `d[k] = v`, and `d[k] += v`, changes the collection in place (§7.17); its value may be a bare comma-list, so `ts[0] = 3, 4` writes a tuple. An element or a slice may also be one target of several, in a target list, a chained assignment or a `for` header, written the same way. **Chained assignment** `a = b = ` binds every target to one value (evaluated once), left to right: `a = xs[0] = 5` binds `a` and writes `xs[0]`, and `xs[a] = a = 1` writes `xs` at the `a` held before the statement. **Unpacking** `a, b = ` destructures a tuple or a list into its targets, each a name, an element or a slice. A `tuple` must hold exactly the target count, written parenthesized (`(1, 2)`) or as a bare comma-list (`b, a`). A `list`, a variadic `tuple` such as a `*args` parameter, a `string`, a `set`, a `dict` (its keys), a dict's view or an iterator gives each target its element type, and its length is checked when the statement runs, so `k, v = line.split("=")`, `x, y = args` and `a, b = "xy"` work and a mismatch is a catchable refusal in CPython's words — `not enough values to unpack (expected 2, got 1)` or `too many values to unpack (expected 2)`, discriminator `value_error`. Anything else, a number included, does not unpack. The whole right side is evaluated before any target binds, so `a, b = b, a` swaps (§7.7), and so does `xs[i], xs[j] = xs[j], xs[i]`; the targets are then assigned left to right, so `i, xs[i] = 1, 5` writes `xs[1]`. A target may itself be a parenthesized target list, so **nested unpacking** `(a, b), c = ((1, 2), 3)` (or `a, (b, c) = …`) destructures a tuple or list of tuples or lists, each level arity-checked independently. One target may be **starred** (`a, *rest = (1, 2, 3)` → `rest = [2, 3]`, always a list): it absorbs the surplus elements (which must share one type), the RHS needing at least the fixed-target count. A parenthesized group may hold its own star, so `a, (b, *c) = 1, (2, 3, 4)` gives `c = [3, 4]`. The star may stand before a group or an element as well: `a, *(b, c) = 1, 2, 3` unpacks what it absorbs, binding `b = 2` and `c = 3`, and with `xs = [[0]]`, `a, *xs[0] = 1, 2` writes the list `[2]` to `xs[0]`. A target list may be written in brackets too, `[a, b] = t`, where one target is a list of one, `[a] = t`, and none is a list of none, `[] = t`, which asks that `t` hold nothing; a chained assignment's target may be a target list, `t = a, b = 1, 2`. An element target may begin with a value no name holds, `f(xs)[0] = v`, and changes the collection that value is, so a script that returns the list it was given hands back the caller's. A field is never a target: written as one, in any of these positions, it is refused in favour of `remember(...)`, and so is an annotation on anything but a name, `xs[0]: int = 1`. ### 7.3 What a value prints as ``` print([3, 1, 2]) # [3, 1, 2] names = list_subjects(Cat) print(names) # 2 entries [felix, tom] proof = why(Cat.tom.old) print(proof) # proof_tree(2) age = current(Cat.felix.age) print(age) # ``` `print` accepts any expressible value and writes it as its text form, the one `str(x)` gives. A boolean is written `True` / `False`. A constructed `list` renders `[e1, e2, …]`; a **tuple** renders `(e1, e2, …)`; a **dict** renders `{k1: v1, k2: v2}` in insertion order; a **set** renders `{e1, e2, …}` in sorted order (an empty set, `set()`) — each element rendered recursively. A read is bound to a name on its own line and the name printed (§7.2), as above. What a read gives back is written too, each type its own way, and this is the whole list ([Python Specification §8.13.1](/spec/dke/python/#how-a-store-derived-value-renders)): | Value | Written as | |---|---| | `claim_list`, `subject_set`, `string_list`, `result_type_set` | a counted preview, `2 entries [e1, e2]` (`1 entry [e1]` for one, `0 entries []` for none): at most 8 elements, then `, + more` | | `proof_tree` | its type name and size, `proof_tree(2)` | | `diagnosis` | the explanation itself, which may span several lines | | `active_claim` | its value alone | | `verify_result` | its type name and verdict, `verify_result match=True`, with ` conflicted` appended when the cell holds competing values | | a read that found nothing | `` | These are for reading, not for parsing: to take a value out of a read, iterate the collection or read the field. The call itself, with several arguments, `sep`, `end` and a `*` spread, is §7.13. ### 7.4 Script call (invocation) ``` audit_subject(K, s) # statement: run for effects, discard any value total = summed(a, b) # value-call: bind what a value script returns total = summed(*pair) # a spread: each element of `pair` an argument ``` A plain call of a stored script: a script is called like any function. The call is fixed when the calling program is compiled and runs the body the callee had then (§9.6); a script cannot call itself, directly or through another. Arguments bind by position, or by **keyword** (`greet("World", greeting="Hi")`); a keyword may not precede a positional argument, and an omitted argument uses the parameter's default (§7.1). A call that does not fit is a `type_error` in Python's words and in Python's order: `summed() got an unexpected keyword argument 'z'`, `summed() takes 2 positional arguments but 3 were given`, or `summed() missing 1 required positional argument: 'b'`. A positional argument written `*xs` **spreads** `xs`: each element is a positional argument of its own, in its place, and a variadic parameter collects what is left over. A spread may follow a keyword argument, as in Python: `summed(b=2, *xs)` fills `a` from `xs`. What may be spread is what `for` walks, and a tuple (§6.13). A tuple written with a fixed number of positions has its length in its type, so a call spreading only such tuples is checked as written: `summed(*(1, 2, 3))`, given two parameters, is a `type_error`. Any other spread has a length known only as the program runs, so the call is checked then, once every argument is evaluated, and one that does not fit is a `refuse` with discriminator `type_error`, in Python's words: `summed() takes 2 positional arguments but 3 were given`, `summed() missing 1 required positional argument: 'b'`, or `summed() got multiple values for argument 'b'` when a keyword names a parameter the spread reaches. Each element must suit every parameter it could fill, and so must each argument written after the spread. An argument written `**d` gives each entry of the dict `d` as a keyword argument, as in Python: `summed(**{"a": 1, "b": 2})` is `summed(a=1, b=2)`. Its keys are known only as the program runs, so the call is checked then, and one that does not fit is a `refuse` with discriminator `type_error`, in Python's words: `summed() got an unexpected keyword argument 'z'`, or `__main__.summed() got multiple values for keyword argument 'a'`. An operand that is no dict is a `type_error`. `print`, a builtin, a built-in type's method, `sorted`, a list's `sort` and a `math` function take a `**` before a dict display whose keys are all literals, its keys given as keyword arguments: `int("10", **{"base": 2})` is `2`, and `xs.sort(**{"reverse": True})` sorts in reverse. A display that gives nothing gives the call nothing, and a key given twice in one display keeps its later value, as in Python. A call it does not fit is a `type_error` where the unit is compiled, in Python's words and order: a keyword two arguments give (`print() got multiple values for keyword argument 'sep'`, and `'1'` for `print(**{1: 2}, **{True: 3})`, a key that is no string being one key as a dict's are equal), then a key that is no string (`keywords must be strings`), then a name the callee does not take (`'zz' is an invalid keyword argument for print()`, `len() takes no keyword arguments`). Any other operand after a `**` there, such as a name, is read when the call runs at `print` (§7.13), at a builtin that takes no name, such as `len`, `abs`, `list` or `isinstance`, and at `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum`, `sorted`, `min`, `max` and `dict`, at a built-in value's method, a change among them, and at a function of `math`, `cmath`, `vector` or `struct`, and not yet anywhere else. At a builtin that takes no name it is read after the positional arguments, and a mapping that gives nothing leaves the call as written, so `len(xs, **opts)` is `len(xs)` while `opts` is empty. Any name it gives is a catchable refuse with discriminator `type_error`, `len() takes no keyword arguments`, after a name given twice (`len() got multiple values for keyword argument 'x'`, or `'1'` where two mappings give the key `1`) or, once every mapping is merged, a key that is no string (`keywords must be strings`), and a display written beside such a `**`, whose key is no string, is merged with it as the call runs: `print(**{1: 2}, **n, **k)` with `n` and `k` both holding `"x"` is `print() got multiple values for keyword argument 'x'`. Two displays written out that give one name are refused as the call runs too where a `**` the program reads is merged before the second giving, in the order Python merges: `print(**n, **{"x": 1}, **{"z": 1}, **{"z": 2})` with `n` holding `"x"` is `print() got multiple values for keyword argument 'x'`, and the refusal is catchable; with every mapping written out it is refused where the unit is compiled. A count it does not take is refused only where the mapping gives no name, as the program runs: with `opts` empty, `len(xs, 2, **opts)` is `len() takes exactly one argument (2 given)`. So it is at a built-in value's method and a function of a built-in namespace that take no name, `s.upper(1, **opts)` and `math.sqrt(x, 2, **opts)`, and without such a `**` the count is a `type_error` where the unit is compiled. So is an argument it does not take, of a type or a value it refuses: with `opts` holding `x`, `len(5, **opts)` is `len() takes no keyword arguments` and `s.center(1.5, **opts)` is `str.center() takes no keyword arguments`, and with `opts` empty each is refused in the words it has without such a `**`, `object of type 'int' has no len()`, as the program runs. So is a change's, such as `s.add([1], **opts)`, and a dict's `get` or `setdefault` of a key alone, which has no `None` to answer, once the names are merged. So is a change that would give its receiver a second type: after `s = {1}`, `s.add("a", **opts)` is `set.add() takes no keyword arguments` with `opts` holding `x`, and with `opts` empty the refusal a name's one type gives, as the program runs; the statement refuses either way, so the receiver keeps its type. Beside a `*` whose length is known only as the program runs, it is read where what is called takes no name, a builtin, a function, a built-in value's method or a change, after every argument and before the count the `*` gives: with `opts` holding `x`, `len(*xs, **opts)` is `len() takes no keyword arguments` whatever `xs` holds. So it is at `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum` and `dict`, which take names, over the count the `*` gives and in the order below: with `xs` empty and `opts` holding `base`, `int(*xs, **opts)` is `int() missing string argument`. So it is at `sorted`, `min` and `max`, which count the `*` before they read a name, as Python's do: over two elements `sorted(*xs, **opts)` is `sorted expected 1 argument, got 2` whatever `opts` holds. So it is at a built-in value's method that takes names, `split` and its kin, and at `math.isclose`, over the count the `*` gives. So it is at a list's `sort`, which counts what the `*` gives as arguments by position and refuses any, `sort() takes no positional arguments`, and at a dict's `update`, which writes what the `*` gives and then the names, as keys. So it is through a type's name, where the receiver is the first thing the `*` gives, and the names are merged before the receiver is taken, as Python merges them: with `xs` empty and `opts` holding a key that is no string, `list.sort(*xs, **opts)` and `str.upper(*xs, **opts)` are `keywords must be strings`, and otherwise `unbound method list.sort() needs an argument` and `unbound method str.upper() needs an argument`; where `xs` gives a value of another type first, `descriptor 'upper' for 'str' objects doesn't apply to a 'int' object`. So it is where nothing types what the `*` gives, as with `xs = []`. At `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum`, `sorted`, `min` and `max` the names it gives are the call's, as if written: `int(s, **opts)` with `opts` holding `base` is `int(s, base=...)`. A name the callee does not take is a catchable refuse with discriminator `type_error`, `'x' is an invalid keyword argument for int()`, after too many names in all (`int() takes at most 2 arguments (3 given)`) and after a name the call lacks or is given by name and by position, as in Python. Any other refusal is the one the call written out meets, as the program runs. `enumerate` reads the names where they stand, as Python's does: with two arguments in all a name must be `start`, and with one it must be `iterable`, so `enumerate(**opts)` with `opts` holding `start` alone is refused `'start' is an invalid keyword argument for enumerate()`, as `enumerate(start=1)` is. `sorted`, `min` and `max` count the names apart from the positional arguments, and `sorted` is refused under `sort`'s name, as Python's is. A `key` a mapping gives is never a function, since a dict holds none, so the call refuses it, `'int' object is not callable`, once there is an element to call it on, and over none is the call without it. `dict` takes every name as a key, after its positional argument's entries. A call whose type would turn on the names given is a `type_error` where the unit is compiled: `round(x, **opts)` over a `real` gives an `int` without `ndigits` and a `real` with it. A call that no names its mapping could give would let run is refused as the program runs, in the words of what the mapping gives, as Python's is: `sum(**opts)` is `sum() takes at least 1 positional argument (0 given)`. At a built-in value's method the names are the method's, as if written: `split`, `rsplit`, `splitlines` and `expandtabs` take theirs as Python's do, and any other method refuses any name it is given, `str.upper() takes no keyword arguments`. So does a method that changes its receiver: a list's `sort` takes `key` and `reverse` as `sorted` does, a dict's `update` takes every name as a key, after the entries it is given, and any other refuses any name before the collection changes, `list.append() takes no keyword arguments`, and a count it does not take only where the mapping gives no name. Read as a value and bound to no name, such a change is made, and only then is the value refused, as the program runs: with `opts` empty, `print(xs.append(2, **opts))` appends `2` and is refused. A name written beside the `**` is merged with the names it gives, so that with `opts` holding `x`, `xs.append(5, x=1, **opts)` is `list.append() got multiple values for keyword argument 'x'`. Through a type's name, a receiver written of another type is refused once the names are merged, before the method reads a name: `str.upper(5, **opts)` is `keywords must be strings` where `opts` holds a key that is no string, and otherwise `descriptor 'upper' for 'str' objects doesn't apply to a 'int' object`. Without such a `**` each is a `type_error` where the unit is compiled. Through its type a call given no positional argument, only such a `**`, merges the names and is then refused for its missing receiver: `str.upper(**opts)` is `unbound method str.upper() needs an argument`, after `keywords must be strings` or a name given twice. A name written beside such a `**` at `sorted`, `min`, `max` or a list's `sort` that the callee does not take waits for the merge too: with `opts` holding `x`, `sorted(xs, x=1, **opts)` is `sorted() got multiple values for keyword argument 'x'`, and with `opts` empty `'x' is an invalid keyword argument for sort()`. A list's `sort` whose receiver is written, with a typed `*` after it, counts what the `*` gives before it names one: `xs.sort(*ys, x=1)` and `list.sort(xs, *ys, x=1)` are `sort() takes no positional arguments` where `ys` gives a value and `'x' is an invalid keyword argument for sort()` where it gives none, as the program runs; where nothing types `ys` the second is refused where the unit is compiled. A positional argument written beside the `*` is counted with what the `*` gives, before any name: `xs.sort(*ys, 5, x=1)` is `sort() takes at most 2 arguments (3 given)` where `ys` gives one value, as the program runs, and where nothing types `ys` it is refused where the unit is compiled. A `key=` beside the `*` is applied only where the sort runs: `xs.sort(*ys, key=len)` over a list of `int`s is `sort() takes no positional arguments` where `ys` gives one value, and `object of type 'int' has no len()` where it gives none, each as the program runs and catchable; where nothing types `ys`, and where there is no `*`, a key the elements refuse is refused where the unit is compiled. A list's `sort` through its type takes its list from the first thing every `*` and every positional argument gives, in order, and counts and names what follows it only after: `list.sort(*xs, ys)` sorts `ys` where `xs` gives nothing, `list.sort(*xs, x=1)` with `xs` giving nothing is `unbound method list.sort() needs an argument`, and over a tuple `t`, `list.sort(t, 1)` is `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object`. At a function of a built-in namespace the names are merged under the function as Python's call names it, `math.isclose()` or `_struct.pack()`: `math.isclose` takes its names as `int` does above, named alone, `isclose() missing required argument 'b' (pos 2)`, and any other function refuses any name, `math.sqrt() takes no keyword arguments`, and `math.log` before it reads a key, `log() takes no keyword arguments`. A call of `slice` refuses any name before it reads a key too, `slice() takes no keyword arguments`. The three methods a type answers itself, `dict.fromkeys`, `str.maketrans` and `float.fromhex`, read a `**` the same way, named with their type: with `opts` holding `x`, `dict.fromkeys(xs, 0, **opts)` is `dict.fromkeys() takes no keyword arguments`, and with `opts` empty `dict.fromkeys(xs, 0)`. A value bound from such a call, where nothing types what the call would answer, is a `type_error` where the unit is compiled, in the call's own words, whatever the mapping gives, since a name bound by bare assignment takes its value's type and such a call has none: `v = list.pop((1, 2), **opts)` is `descriptor 'pop' for 'list' objects doesn't apply to a 'tuple' object`, `v = chr("a", 2, **opts)` is `chr() takes exactly one argument (2 given)`, `v = abs("a", **opts)` is a `type_error` as `v = abs("a")` is, since `abs` answers the type it is given, and over `xs = []`, `v = list.pop(*xs, **opts)` is ``cannot infer type of binding `v` — right-hand side did not typecheck to a known type``. The same call read as a statement or printed is refused as the program runs, as above, and so is a value bound from a call that fixes a type, such as `str.upper(5, **opts)`, `len(5, **opts)` or `s.center(1.5, **opts)`. A **value script** (§7.1) may be called in **expression position** — its call yields what the script `return`s, and composes anywhere an expression may: `y = f(x)`, `return f(x)`, `print(f(x))`, `f(a) + g(b)`, or nested `f(g(x))` (evaluated inner-first, left-to-right). A **void script** called as a value is a `type_error` — call it as a statement instead. A call names what it calls: `(…)` after a value — a literal, or what an index, a call or a `.name` answers, `xs[0](1)` or `f()(1)` — calls that value, and is a `type_error` in Python's words, `'int' object is not callable`, since no value holds a function. The name of a `def`, a method or a module read with no call is a `type_error` saying so. A builtin called as a statement, where no script has its name, runs and its value is dropped, as any expression alone is: `len(xs)` does nothing you can see, and `next(e)` (§6.12) reads an element and discards it, since the read changes `e`. ### 7.5 Verb-call statement A write verb (§5.1) in statement position: ``` remember(K.s.color, "orange", "op1") ``` ### 7.6 `if` ``` if : elif : else: ``` `` may be any value — it is tested for **truthiness** (§6.2), so `if xs:` / `if s:` / `if n:` branch on non-empty / non-zero as in Python; each `elif` and the `else` are optional. An `elif` is equivalent to an `else:` containing a nested `if`. A name first bound in both arms of an `if`/`else`, or in every arm of a chain ending in `else`, is readable after it; one bound in some arms only is not, and an arm ending in `return`, `raise`, `break` or `continue` need not bind it ([Python Specification §6.1](/spec/dke/python/#scoping)). ### 7.7 `for … in` ``` for c in hist: ``` Iterates one of the six **finite collections** — `claim_list`, `string_list`, `subject_set`, `result_type_set`, `proof_tree` and `diagnosis` (§4.1) — a constructed `list` (§6.7) such as `range(n)` (§6.12), a `dict` (binding each **key** in insertion order, §6.9) or one of its views, or a `set` (in sorted order, §6.10), or a `string`, binding each **character** (a one-character string, by Unicode character), or a `tuple` whose elements all have one type (a variadic tuple always, a fixed one when its positions share a type). Written without its brackets, as in Python, it is the same tuple: `for x in 1, 2:` iterates `(1, 2)`. A fixed tuple with differing position types, and any other iterand, is a `type_error`. An empty `[]` or `{}` whose element type no context supplied (§6.7) iterates too, zero times, as in Python: `for x in []:` compiles, and the code that reads `x` is not type-checked, since it never runs; the rest of the body is. If an enclosing loop could give that list, set or dict elements on a later pass (`xs = xs + [1]` in the same outer loop), the program is refused: declare the type where it starts, `xs: list = []`, `seen: set = set([])` or `d: dict = {}`. The loop variable stays bound after the loop and in its `else`, holding the last element (an empty loop leaves it as it was), as in Python. So a `for` over a name that is already a variable must keep its type. After `x = 1`, the loop `for x in ["a"]:` would leave a string in an `int` variable, and is refused with a `type_error` that names it. → [Python Specification §6.1](/spec/dke/python/#scoping), [§6.3](/spec/dke/python/#bounded-iteration) The collection is snapshotted at entry. A comprehension (§6.7, §6.11) is the same bounded iteration in expression position. Bounded iteration is what gives DKE Python its termination guarantee. A **target list** unpacks each element (a tuple or a list) into named variables — `for i, x in enumerate(xs):`, `for a, b in zip(xs, ys):`, `for k, v in d.items():` — each element must be a `tuple` of the target arity, or a `list` or a variadic tuple, whose length is checked as the element is reached. A trailing comma ends the list, so `for a, b, in xs:` is `for a, b in xs:` and `for a, in xs:` binds `a` to each one-element tuple's item. A target, alone or in a list, may be an element or a slice: `for xs[0] in ys:` writes `xs[0]` with each element before the body runs. **Nested** targets (`for a, (b, c) in xs:`) and one optional **starred** target (`for a, *rest in xs:`) are taken here as they are in an assignment, the star making the target arity a minimum. A name first bound in the body is not readable after the loop, which may run zero times. Like the single loop variable, the targets stay bound after the loop, holding the last element's values. Inside a `for` body, **`break`** stops the loop and **`continue`** skips to the next element (each resolves to the innermost enclosing loop; both may sit inside a nested `if`/`match`/`try`). Either outside a loop is a compile-time error, and a `break`/`continue` must be inside a loop in its **own** `def`, not merely called from one. Both only shorten the (bounded) iteration, so termination still holds. ``` for c in claims: if c.value == "": continue # skip blanks if c.value == stop: break # stop at a sentinel print(c.value) ``` A `for` may end with an **`else:`** clause: it runs once after the loop completes, but **not** if a `break` left the loop. An empty collection (zero iterations) still runs it. The loop variable is still bound in the `else`, holding the last element, as it is after any loop (above). ``` for c in hist: print(c.value) else: print("no more history") # runs unless a break fired above ``` ### 7.8 `match` (discriminator dispatch) ``` match cur: case active_claim: print("value = " + str(cur.value)) case empty: print("absent") ``` The scrutinee must be a discriminator (`active_claim`, `proof_tree`, `verify_result`). Arms **exhaustively cover** the discriminator's cases. `case _:` is the catch-all `case default:` is, and may stand in any `match`. (A `match` over a value Python has is Python's, each arm a test and none required, §7.8; over an **instance** the compiler follows it is also the class match, §8.6.) A `match` whose scrutinee is a `value` (a claim's `.value`, §4.1) is a **value match**: the case labels are the value classes `int`, `bool`, `string`, `datetime`, `duration`, `real`, `null`, `blob`, `scientific` and `complex`. The write-producible classes (`int`, `bool`, `string`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex`) may bind the typed content with a named `as ` arm; the one read-only class, `null`, is a bare arm only — attaching `as ` to it is a type_error: ``` match cur.value: case int as n: print("next = " + str((n + 1))) # n : int case string as s: print("text = " + str(s)) case duration as u: print("span = " + str(u)) # u : duration case real as r: print("value = " + str(r)) # r : real — renders decimal case blob as b: print("bytes = " + str(b)) # b : blob — renders hex case null: print("an absent value") # bare — no `as` case default: print("other class") ``` Arms cover every class or provide `case default:`; a bound `as ` is typed at the arm's class and scoped to that arm. A `bool` is an `int`, so a `case int` arm takes one and covers `bool`, and a `case bool` below an unguarded `case int` is refused as an arm that could never run — put `case bool` first. `case int():`, Python's class-pattern spelling, tests what `case int:` tests, and `case str():` and `case float():` are the `string` and `real` arms. When every arm naming a class writes the parentheses, and none is a bare label, the `match` is Python's instead: each arm a test and none required, and `case int(n):` binds the value to `n`, as a capture binds it. Beside a bare label, a pattern inside the parentheses is not taken — bind the content with `as`. In an arm naming classes, a scrutinee written as a name is read at their class (§8.11), and in an arm below them at what their tests failing leave it. A label that is no class, such as `null` or `default`, takes no parentheses. ``` for e in [1, "a"]: match e: case int(): print(e + 1) # e : int case str(): print(e.upper()) # e : string ``` **Alternatives in one arm.** A case label may name two or more classes separated by `|`, and the arm runs when the value is any one of them: ``` match cur.value: case int | real | scientific: print("a number") case string: print("text") case default: print("something else") ``` `case int | real:` and two arms `case int:` / `case real:` with the same body mean the same thing; the alternation is a spelling, not a widening. An alternation takes no `as ` — the alternatives bind different types, so there is no single type for the name — and `default` may not appear in one, since it already covers everything not named. There is no AND-pattern: a value has exactly one class. **Guards.** An arm may carry a condition it must also satisfy: ``` match cur.value: case int as n if n > 100: print("big") case int as n if n > 0: print("positive") case int: print("zero or negative") case default: print("not an int") ``` Arms are tried in order and the first whose class matches and whose guard holds runs; a false guard falls through to the next arm. The `as` binding is in scope in the guard, which is what makes `if n > 0` possible. The condition is anything with a truth value, the same rule `if` uses. The body reads a name its guard narrows at the narrower class (§8.11). Two things follow, and both are compile-time errors when broken. **A guarded arm does not count towards exhaustiveness** — its guard may fail — so a `match` with no `case default:` must name every class in an arm with no guard; that is what guarantees an exhaustive `match` always runs exactly one arm. And a class may be named by several arms only while every earlier one is guarded: an unguarded `case int:` runs for every int, so nothing after it can. `case default:` may not carry a guard at all. There is no AND-pattern: a value has exactly one class. **Matching on a value.** A `case` label can be a value pattern — Python's own — and then the `match` compares rather than dispatches: ``` match n: case 0: print("none") case 1 | 2: print("a couple") case k if k < 0: print("negative " + str(k)) case _: print("several") ``` A **literal** — a number (negative included), a text, `True` or `False` — matches when the scrutinee `==` it, so `case 1:` matches `1.0`. Literals joined by `|` match any of them. `_` matches anything and binds nothing; a **name** that is not a class label matches anything and binds the value to that name, and `as ` after any pattern does the same. A capture and an `as` over a value pattern bind like ordinary assignments — made before the guard runs, kept when the guard is false, and §7.8's scoping note applies to them, not the arm-local rule the class arms use. A `match` over a value Python has — a number, a text, a collection, a `slice`, a view, an iterator or an instance — takes value patterns, shape patterns and class patterns, and **needs no catch-all**: when no arm matches, none runs and execution continues after the `match`, exactly as in Python. An arm that can never be taken compiles and never runs. `case int():`, `case Site():` and `case geo.Site():` test the class, as `isinstance` does, at the top of an arm or inside another pattern (`case [int(), Site() as s]:`, `case 1 | str():`), and a guard and a body read the value as that class. A type that reads as itself, `int`, `bool`, `float`, `str`, `list`, `tuple`, `dict` or `set`, may hold one pattern over the same value (`case int(n):`); past that, and for anything inside another class's parentheses, the test raises Python's `type_error` once the class has held (`int() accepts 1 positional sub-pattern (2 given)`). Over a `value`, literals stand beside the class arms and are tried where they are written — a `match` naming a class is still exhaustive, and one naming none is not. A catch-all — `_`, an unguarded capture, or `case default:` beside a value pattern — must be the **last** arm, since nothing after it could run. `case True:` takes only a `bool`, by identity, as in Python (`case 1:` over a `bool` does take `True`, since `1 == True`). What is refused, and why: a class named with no `()` where the compiler does not follow an instance to it (`case int:` over an `int`), which Python reads as a capture — write `case int():`; a keyword in a class pattern (`case Site(size=1):`), which would read a field while the `match` dispatches — read it inside the arm; a typed value (`datetime("…")`, `duration("…")`, `blob("…")`, `scientific("…")`), which reads as a class pattern, and a value class takes no sub-patterns; a capture in a `match` that dispatches on classes, where a misspelt class must not silently take everything; and mixing a class and a value in one arm (`case 1 | int:`). `case 1 | n:` is refused as Python refuses it — a capture cannot be one alternative — and `case _ | 1:` because nothing follows `_`. An `as _` is a syntax error, since `_` binds nothing, and a pattern binding one name twice is refused, as `case [a, a]:` is. **Matching on shape.** A `case` label can be a shape instead of a class — a list pattern or a dict pattern — and then the `match` is matching the structure of a collection rather than the class of a stored value: ``` match parts: case []: print("nothing") case [only]: print("just " + only) case [first, *rest]: print(first + " and " + str(rest.length) + " more") case default: print("unreachable, but required") ``` A name in a pattern binds whatever is at that position, and `_` matches it and binds nothing; a literal tests for equality and binds nothing; a nested `[...]` or `{...}` matches a shape at that position. Alternatives separated by `|` can stand at a position, as in `case [1 | 2, b]:`, and are tried in order. Each binds the same names, so `case [[1, a] | [2, a, 0]]:` binds `a` from whichever matched. `as name` after a pattern at a position binds what it matched, as in `case [[1, y] as z]:`. Alternatives and `as` stand at the top of a `case` as well, around a shape: `case [1] | [2]:`, `case 1 | [b, 2]:`, `case [1, _] as z:`. A `*name` collects the remaining elements and **must be the last element**. Without a `*` the pattern matches a list of exactly that length; with one, of at least the fixed length. A dict pattern names keys, each a literal — a text, a number, `True` or `False` — and each key once: ``` match record: case {"id": i, "kind": "sensor"}: print("sensor " + i) case {"id": i}: print("something " + i) case default: print("no id") ``` It matches when every key it names is present, and ignores keys it does not name — it is a subset test. As with a list pattern, `i` binds the value and the literal `"sensor"` tests it. A shape arm matches a sequence or a mapping and nothing else: over any other value it compiles and never matches, and as one alternative of several it is left out, the arm standing on the others: over `x = 1`, `case 1 | [2]:` is taken. The thing matched on may be any expression, evaluated once, and a shape `match` **needs no catch-all**. Shape arms take guards (`case [a, b] if a > b:`) with the pattern's bindings in scope, and may stand beside value and class patterns in one `match`. **An ordinary assignment inside an arm is not arm-scoped**, and the contrast is worth stating because one `match` has both rules in it. The `as ` binding of a CLASS arm exists only inside its arm (a value pattern's capture and `as` are assignments, above); a name you assign in an arm belongs to the enclosing scope, and is readable after the `match` when it was bound before it or when every arm assigns it and the `match` has a catch-all ([Python Specification §6.1](/spec/dke/python/#scoping)), which is how a match computes a value for the code below it. Reading the arm binding after the `match` is a `type_error`, `name 'n' is not defined`; reading the assigned name is ordinary. ``` label = "none" match cur.value: case int as n: label = "int " + str(n) # `n` lives only here case default: label = "other" print("after: " + label) # `label` is readable; `n` is not ``` ### 7.9 `try` ``` try: except refuse as r: except engine_error as e: else: # runs only if the body raised nothing finally: # always runs, last ``` Python's own `except … [as …]:` shape. Both handlers are optional, and so is each handler's `as ` binding — `except refuse:` catches without naming the exception (omit `as` when the body does not use the refuse/engine_error info). A refused statement does not bind and transfers control to `except refuse`; an engine error to `except engine_error`, which takes every run-time failure the wire reports as `engine_error`, including those that until now passed by it (a value narrowed to a class it did not hold, a read of a method that returns nothing); a spent budget, a stopped run and an add-only refusal remain uncatchable. With no matching handler, the outcome propagates. Handlers may be written in either order, and a category may be named more than once — the **first** handler naming the raised category runs, as in Python, and a later handler for that same category never runs. The program still compiles, and `compile` and `run` each answer with a **note** naming that handler and the line of the one that runs, after the result and as a `notes` entry the [**MCP Reference**](/ref/dke/mcp/) describes. An optional **`else`** (allowed only alongside an `except`) runs when the body raised nothing; a refuse in the `else` is not caught here. An optional **`finally`** runs on every exit path — clean, caught, propagating, or a `break`/`continue` leaving the block — and runs last; if the `finally` itself raises or breaks, that supersedes. A `try` needs at least one `except` or a `finally`. A name first bound in the body and in every handler is readable after the `try`, the `else` reads what the body bound, and a name the `finally` binds is readable after it; the `finally` itself reads none of the body's names, since it also runs when the body did not complete ([Python Specification §6.1](/spec/dke/python/#scoping)). ``` try: remember(Reading.probe.taken_at, "not a timestamp", "sensor") except refuse as r: print(r.reason) if r.teaching_hint != "": print(r.teaching_hint) # empty when there is no suggestion except engine_error as e: print(e.reason) ``` Report `.reason`; add `.teaching_hint` when it is non-empty, since it says how to correct the call. Branch on `.discriminator` (or an engine error's `.code`) only where the program must act differently — see §4.2. ### 7.10 `branch` / `commit` / `rollback` ``` with branch("experiment"): remember(K.s.color, "tigret", "audit") if ok: commit else: rollback ``` A `with branch(""):` block groups writes transactionally: clean exit commits; an uncaught refuse / engine error rolls back. Leaving the block early by `return`, `break` or `continue` is a clean exit and commits, as reaching the end does — the line is between leaving and failing, not between leaving early and leaving at the end. To abandon the writes, say `rollback`. Bare `commit` / `rollback` end the branch explicitly — the branch is over at that statement, so a write in the block's remaining lines is an ordinary, immediately durable one. Ordinary invocation is **not** transactional. An `update` inside the block revises a claim its source recorded before the branch as it would outside one, and a `remember` after it is judged against the new claim: ``` remember(Plant.basil.min_moisture, 40, "care-guide") with branch("bonus"): update(Plant.basil.min_moisture, 40, "care-guide", null, datetime("2026-10-31T23:59:59Z")) remember(Plant.basil.min_moisture, 25, "care-guide", datetime("2026-11-01T00:00:00Z"), null) # October reads 40 and January reads 25, in the block and after it ``` What the store's rules concluded from the claim from before the branch no longer reads inside the block either, as after a `commit`, while a conclusion the rules still reach from what the block reads does. A fact the block writes does the same to what the rules concluded from that fact's absence. The claim from before the branch is not changed, and neither is what was concluded from it, so a `rollback` leaves the store exactly as it was. → §5.1 A branch's name begins with a letter or `_` and continues with letters, digits, `_` or `-`; it holds no `.` or space and cannot be empty. Any other name is a `refuse` naming the character at fault, before the block runs. A branch may not take the name of a world that exists, and cannot open inside a `world` block (§7.18). ### 7.11 `import` (module) ``` import catalog # in a program body: load a stored module ``` A DKE Python file is a **module** (§9); `import ` **loads** one: it runs the module's body once and makes its names available under the module's namespace. `import` is a reserved word, as it is in Python, and begins the import statement at statement head. A module may `import` another module; an `import` cycle is rejected when the module loads. Loading a module already loaded **reconciles** it: only the cells whose values changed are rewritten, so re-import is safe to repeat. → §9.1, §9.3, §9.5 **A module's names are namespaced.** A kind a module declares with `class` is addressed under the module name — `catalog.Product`. The **module-qualified construction** `module.Kind(subject)` is an instance of it, and you read a cell through the instance's field, as with any class: ``` p = catalog.Product("widget").price # read catalog's Product widget, field price print("price = " + str(p.value)) ``` You can also bind the instance first. After `w = catalog.Product("widget")`, `w.price` reads the same cell, and `w` can be passed to a `catalog` script that takes a `Product`. Two modules may each declare a `Product` without collision (`catalog.Product`, `store.Product`, distinct). A **script** a module declares with `def` is addressed the same way — `module.script` — and that qualified name is how the service surface names it: the name `info` and `forget` take, the name `list` reports, and the name instructions call once the module is imported (`warehouse.restock`, `store.restock`, distinct). A script's own body still calls its siblings unqualified; the qualifier is the address, used from outside at the service surface. → §9.6 **An `import` also brings the module's kinds into scope unqualified**, so after `import catalog` a bare `Product` means `catalog.Product`: ``` import catalog def restock(): remember(Product.widget.price, "9", "ops") # catalog's Product ``` A kind the unit declares itself wins over an imported one of the same name, and if two imported modules declare the same kind the program is refused — name the module to say which you mean (`catalog.Product`). That rule covers a kind in a path. A class is constructed only through its module: after `import catalog`, a bare `Product("widget")` is refused, just as a bare name is after `import catalog` in Python. → §9.6 **Provide a module from the wire.** `compile` with a `source` stores a module by name and runs it (§5.7). Every module in a store arrived that way. The bare `import ` is the in-language form written in a program. → §9.3, §9.4 **Data at module scope.** A write verb (§5.1) written at a module's top level — not inside a `def` — is a **data declaration**: it runs when the module is compiled or imported. A module that only declares a schema, installs a rule, or writes data defines no callable script and is loaded for its body's effect. → §9.2, §9.7 ``` # in a module body, at top level: remember(Rate.vat.pct, 25, "2026-finance-act") # runs when compiled or imported ``` ### 7.12 `return` ``` def classify(n: int) -> string: if n > 100: return "big" else: return "small" # every path returns → value script (§7.1) def find(xs: claim_list, stop: string): for c in xs: if c.value == stop: return # bare early exit → any script print(c.value) ``` A **bare `return`** ends the current script early and is valid in a script returning no value; outside a script it is refused, as Python refuses it. A **`return `** yields a value and makes the script a **value script** (§7.1); the expression's type must match the declared `-> type` (an `int` satisfies `-> real`), and with none, the `return`s give the type, the wider of two where one fills the other. A value script must `return` on **every** path — a `return` in its suite, or an `if/else` (or exhaustive `match`) where each branch returns; a `for` alone never satisfies it. Two `return`s of types neither of which fills the other, or a value script that can fall off its end, are a `type_error`. A `return` exits past any enclosing `for`/`try` (running a `try`'s `finally` first) and never runs a `for … else`. ### 7.13 `print` ``` print("hi", who) # one line: "hi " print(1, 2, 3) # "1 2 3" print("a", "b", sep=", ") # "a, b" print("x", end="!") # "x!" print(*[1, 2], 3, sep="-") # "1-2-3" print(1, 2, sep="-" * 2) # "1--2" print("x", flush=True) # "x" print("a", "b", **{"sep": "-"}) # "a-b" opts = {"sep": "-", "end": "!"} print("a", "b", **opts) # "a-b!" print() # an empty line ``` Writes one output line. Each argument is written as its text form (§7.3) and the pieces are joined by `sep` (default a single space), then `end` (default a newline) is appended. An argument written with `*` spreads a collection, and each of its elements is an argument of its own (§6.13). `sep` and `end` are keyword arguments, each any expression that gives a string, read after the positional arguments in the order written. `None` written as the whole of either is its default, and one that gives anything else is a catchable refuse when the line is composed, "sep must be None or a string, not int". `flush` is read and changes nothing, and `file` may be given `None` alone, its default. A keyword `print` does not take is a type error in Python's words (`'x' is an invalid keyword argument for print()`), the first one written, followed by the ones it takes. A `**` before a dict display gives these keywords as its keys (§7.4). A `**` before any other dict, such as a name that holds one, gives them as the line is composed, merged where it was written, as Python merges a call's keywords: a name given twice (`print() got multiple values for keyword argument 'sep'`) is refused as it is met, and a key that is no string (`keywords must be strings`) once every mapping is merged, then the first name `print` does not take, written or given, in Python's words alone, then the values, `sep` before `end`. Each is a catchable refuse with discriminator `type_error`, and a `file` it gives is refused in the words a written one is given. An operand that is no dict is a type error where the unit is compiled (`print() argument after ** must be a mapping, not list`). Output is line-oriented — each `print` writes exactly one line — so a trailing newline in `end` is that line, and `end=""` reads the same as the default (there is no continuing a line). ### 7.14 `pass` ``` if x > 0: print("positive") else: pass # do nothing ``` A no-op statement: it does nothing and produces no output. Use it where a block would otherwise be empty. ### 7.15 `raise` ``` def withdraw(amount: int): if amount <= 0: raise ValueError("amount must be positive") remember(Account.main.pending, amount, "teller") try: withdraw(x) except refuse as r: print("blocked: " + str(r.reason)) # "blocked: amount must be positive" print("kind: " + str(r.discriminator)) # "kind: ValueError" ``` Raises a **recoverable error** — a `refuse` — that an enclosing `try` catches with `except refuse as r:`. Name one of the built-in exceptions (`ValueError`, `KeyError`, `TypeError`, `RuntimeError`, …); the message becomes `r.reason` and the exception name `r.discriminator`. Several arguments are held as Python holds them, and `r.reason` is the text of their tuple. Every exception name behaves the same — there are no custom exception types. A `raise` never falls through, so a branch that ends in one satisfies a value script's "return on every path". `raise` alone raises again what is being handled, in an `except` or in a `finally` with one pending, keeping its category, reason and discriminator; with nothing being handled it is `RuntimeError: No active exception to reraise`. `raise e` raises what a handler's `e` holds. `raise X from e` and `raise X from None` read as Python's do; nothing here reads a cause back, so a cause changes nothing a program can observe, and one that is neither is a `type_error`, `exception causes must derive from BaseException`. ### 7.16 `assert` ``` def withdraw(amount: int): assert amount > 0, "amount must be positive" remember(Account.main.pending, amount, "teller") assert x == 5 # passes silently, execution continues try: assert x > 100, "too small" except refuse as r: print("caught: " + str(r.reason)) # "caught: too small" ``` Python's assertion. `assert cond` evaluates the `bool` `cond` and, when it is false, raises an `AssertionError` — a `refuse`, equivalent to `if not cond: raise AssertionError`. `assert cond, msg` records `msg` on `r.reason` (evaluated only on failure); a bare `assert cond` gives an empty reason. A passing assertion produces no output and execution continues. A condition may begin with a bracket: `assert (x)` and `assert (x), "m"` are this statement. It is distinct from the absence-assertion **verb** `assert(K.s.a, src)` (§5.1), which records an explicit-absent claim, and which is read where the bracket ends the statement and holds a dotted name and then a comma. So `assert (x.y, "m")`, a tuple that never fails in Python, is the verb here. ### 7.17 Changing a collection ``` xs = [3, 1] xs.append(2) # xs is [3, 1, 2] xs.sort() # [1, 2, 3] xs[0] = 10 # [10, 2, 3] del xs[-1] # [10, 2] xs[1:] = [7, 8, 9] # [10, 7, 8, 9] del xs[::2] # [7, 9] tags = {"red"} tags.add("blue") # {'blue', 'red'} ages = {"ana": 30} ages["bo"] = 25 # {'ana': 30, 'bo': 25} ages["ana"] += 1 # {'ana': 31, 'bo': 25} del ages["bo"] # {'ana': 31} ``` A list, a set and a dict are **shared**: `b = a` binds `b` to the list `a` holds, not to a copy, and a script called with a list receives that list. A statement that changes one changes it **in place**, so every name, parameter and container holding it sees the change, as in Python. Each answers nothing and is a statement of its own — `ys = xs.append(2)` is an error — save `pop`, `setdefault` and `popitem`, which answer a value too (below). On a **list**: `xs.append(v)`, `xs.extend(c)` (the elements of a list, tuple, set, string or dict), `xs.insert(i, v)` (its position clamped, a negative one counting from the end), `xs.remove(v)` (the first equal element), `xs.sort()` with `sorted`'s optional `key=` and `reverse=`, `xs.reverse()`, `xs.clear()`, `xs[i] = v`, `del xs[i]`, a slice write `xs[i:j] = c` (any collection, more or fewer elements; with a step, exactly as many as the slice selects), `del xs[i:j]`, `xs += c` (any collection, as `extend`) and `xs *= n`. On a **set**: `s.add(v)`, `s.discard(v)`, `s.remove(v)`, `s.update(c)`, `s.difference_update(c)`, `s.intersection_update(c)`, `s.symmetric_difference_update(c)`, `s.clear()`, and `|=`, `&=`, `-=` and `^=`. On a **dict**: `d[k] = v` (a key already held keeps its place), `d.update(e)` (another dict, or `(key, value)` pairs, then any names as string keys: `d.update(e, a=1)`), `d.clear()` and `del d[k]`. An element or slice write evaluates its value before its target, and an augmented one, `xs[i] += v` or `d[k] += v`, its index or key once, as Python does. Each of these changes the collection its target reaches, so it works on an element, or on what a method answers, as on a name: `m[i][j] = v`, `d[k].append(v)`, `del m[i][j:]` — the chain before the last step is read once. It works on a value no name holds as well, read once, `f(xs).append(v)`, `[xs][0].sort()` or `del f(xs)[0]`, and through parentheses, which are the name or element they hold: `(xs).append(v)` and `del (xs)[0]` change `xs`. A list of targets in parentheses is that list, so `del (a, b)` is refused as `del a, b` is. A target that is no name, attribute, element or slice, such as `del f()` or `del xs[0] + 1`, is a parse_error at that target, naming what it is, as in Python. ``` a = [1] b = a a.append(2) # b is [1, 2] as well row = [0] grid = [row, row] row.append(1) # grid is [[0, 1], [0, 1]] r = grid[0] r.append(2) # grid is [[0, 1, 2], [0, 1, 2]] grid[0][0] = 5 # grid is [[5, 1, 2], [5, 1, 2]] groups = {"a": []} groups["a"].append(1) # {'a': [1]} c = a.copy() # a new list: c.append(3) leaves a as it is ``` A literal, a comprehension, `.copy()`, `list(c)`, `set(c)`, `dict(c)`, `sorted(c)`, a slice read and the operators `+`, `*`, `|`, `&`, `-` and `^` build a **new** collection, shallow as Python's is: its elements are the ones its operands hold. To change a collection and keep the original, copy it first — `xs.copy()`, `s.copy()`, `d.copy()` or `dict(d)`. `x = e` binds `x` to what `e` answers and changes nothing `x` held before; `+=` on a number, a string or a tuple binds a new value, as in Python. The changed collection keeps its type, so `xs.append("a")` on a `list` is a type error naming the type change, as `xs += ["a"]` is. A collection first bound empty takes its element type from the first statement that supplies one, for every name holding it: after `a = []`, `b = a`, `a.append(1)`, `b` is a `list`, and `b.append("x")` is refused as `b` sharing its element type with `a`. A `.get()` default supplies a dict's value type the same way: `counts = {}` filled by `counts[w] = counts.get(w, 0) + 1` is a `dict`. An operator supplies it too: `counts[w] += 1` under `if w in counts:` makes `counts` a `dict` (`*` and `%` supply nothing), and a read nothing supplies a type for is refused naming the annotation, `d: dict = {}`. An empty collection written into a collection of collections takes their type: `grid.append([])` on a `list>` and `d[k] = []` on a `dict>` are accepted. A collection built from one still empty (`b = a.copy()`, `b = list(a)`) shares its element type too, since a loop could build it after the source holds elements — Python would let the two differ. A collection cannot hold itself: `a.append(a)` is a type error. A slice write takes any collection, typed as `extend`'s argument is, and a change through an element keeps the element's type: `m[0].append("a")` on a `list>` is refused naming `m[…]`. **Changing and answering.** `pop`, `setdefault` and `popitem` change the collection and answer a value, so each is an expression as well as a statement and works wherever an expression may stand. `xs.pop()` removes and answers a list's last element, and `xs.pop(i)` the one at `i`, a negative `i` counting from the end. `s.pop()` removes and answers a set's first element in its sorted order, where Python's choice follows its hash table. `d.pop(k)` removes `k` and answers its value, and `d.pop(k, default)` answers the default when `k` is absent. `d.setdefault(k, default)` answers the value at `k`, writing the default there first when `k` is absent — DKE Python has no `None`, so the default is required. `d.popitem()` removes the last entry and answers it as a `(key, value)` tuple. A default supplies an empty dict's value type as a `.get()` default does, so `setdefault` groups by key. As a statement the answer is discarded; within an expression the call runs where Python evaluates it. ``` xs = [3, 1, 2] last = xs.pop() # 2; xs is [3, 1] first = xs.pop(0) # 3; xs is [1] ages = {"ana": 31, "bo": 25} bo = ages.pop("bo") # 25; ages is {'ana': 31} cy = ages.pop("cy", 0) # 0; ages is unchanged groups = {} groups.setdefault("a", []).append(1) # {'a': [1]} k, v = ages.popitem() # k is 'ana', v is 31; ages is {} ``` A change the collection cannot make is a catchable **refuse** (§10) in Python's words: `xs.remove(v)` of an absent value is `list.remove(x): x not in list` (discriminator `value_not_in_list`); `s.remove(v)` of an absent element and `del d[k]` of an absent key give the repr of the missing value (`key_not_found`); a list index outside the list on a write or `del` is `list assignment index out of range` (`index_out_of_bounds`); a slice with a step, written with a different number of elements than it selects, is `attempt to assign sequence of size N to extended slice of size M` (`value_error`); a step of `0` is `slice step cannot be zero` (`slice_step_zero`); `xs.pop()` on an empty list is `pop from empty list` and `xs.pop(i)` outside the list `pop index out of range` (`index_out_of_bounds`); and `s.pop()` on an empty set is `'pop from an empty set'`, `d.pop(k)` of an absent key with no default the repr of the key, and `d.popitem()` on an empty dict `'popitem(): dictionary is empty'` (`key_not_found`). **A loop over what its body changes.** A `for` reads a list as it stands at each step, as Python does, so removing elements ends it sooner — but it never runs more steps than the list had when it began, so appending to the list it walks stops there, where Python would go on (the bound is what termination rests on). A list's `extend` over the list itself, by an iterator or a generator expression, stops at the same bound. A `for` over a dict or set walks what it held at the start, and a change of its size is a catchable refuse at the next step in Python's words — `dictionary changed size during iteration` or `Set changed size during iteration` (`changed_during_iteration`). Writing a value to a key already held is not a change of size. A `for` over a dict view (§6.9) reads the dict at each step, taking the element at the position it has reached, so it binds a value written during the loop; removing a key it has passed makes it pass over one, where Python's does not. **One element at a time.** A set's `update` and `difference_update`, and a dict's `update`, read an iterator or a generator expression one element at a time, changing the collection by each before reading the next, as Python's do. One that reads the collection being changed finds it changed and is refused at the read after its size changes, in the same words; what was written before stays, so after `s = {1, 2}` a caught `s.update(x + 10 for x in s)` leaves `{1, 2, 11}`. A dict's pair of the wrong length is refused naming its position, after the pairs before it are written. `intersection_update` and `symmetric_difference_update` read theirs whole first, as Python's do. **Not statements.** `del x` would unbind a name, which stays bound to the end of its script. A string and a tuple take none of these; rebuild them from slices. ### 7.18 `world` ``` create_world("wet") with world("wet"): remember(Field.north.rain_mm, 180, "forecast") r = current(Field.north.rain_mm) print(r.value) ``` A `with world():` block runs its body inside the world named (§5.9). The name is a `string` expression, read once before the body. Entering brings the world up to date with the store; the body reads the store and the world together, and every write in it lands in the world. Leaving the body by any path — its end, `return`, `break`, `continue`, or an uncaught refuse or engine error — leaves the world, and the statements after the block run outside it. Nothing is committed or rolled back on the way out: what the body wrote stays in the world until `merge_world` or `discard_world`. A script called inside the block runs in the world. A `world` block cannot open inside another `world` block or inside a `branch` block, and a `branch` block cannot open inside a `world` block. Written inside one another, these are compile-time errors; reached through a script called from inside one, each is a `refuse`. `world` is a contextual keyword, recognised only after `with`, and an ordinary identifier elsewhere. --- ### 7.19 Named expressions (`:=`) ``` xs = [1, 2, 3] if (n := len(xs)) > 2: print(n) print(a := 1, a) print([y for x in xs if (y := x * 2) > 2], y) ``` `name := value` binds the name when it is evaluated and answers the value, in parentheses, as an element or an argument, and as the whole condition of an `if`, an `elif` or a `case`'s guard, or the whole subject of a `match`, as in Python. Anywhere else, as in `y = a := 1`, it is a parse_error at the `:=`. It binds as an assignment does, so the name keeps its one type, and what is evaluated before it reads the old value. In a comprehension it binds in the scope that holds the comprehension. It is a binding, so it costs no operation, and its value cannot be a read of the store (§7.2). ## 8. Classes, methods, and reasoning Classes group typed **fields** with the **methods** over them and give a typed handle — an **instance** — for one named member (§8.1–8.6). A class may **extend** another, inheriting its fields and methods (§8.10), and `isinstance` tests an instance's class (§8.11). The section then covers the **derivation** features — computed fields (§8.7), standing rules (§8.8), and hypothesis (§8.9) — where the engine records or supposes values on your behalf. The class surface adds no new verbs, and termination is preserved throughout. `class` and `return` are keywords. ### 8.1 Class declaration ``` class Sensor: temp: string # typed field (primitive) def record(self, v: string, src: string): # void method (explicit self) remember(self.temp, v, src) def label(self) -> string: # value method return "sensor" ``` Written `class :`. Fields are `name: type` with a primitive type; names unique within the class. Declaring a class stores nothing. A class may name a single base class it extends — `class Dog(Animal):` — inheriting the base's members (§8.10). A class exists from the line that declares it, as in Python: a line that runs as the module runs and names one below it is refused, `name 'K' is not defined`. The body of a `def` or a method runs when called, so it may name a class declared anywhere in the unit, and a signature may name one in a string, `def f(x: "K"):`. A module statement reaching such a body above the class is refused in the same words. ### 8.2 Instance construction and binding ``` s = Sensor(room) ``` `Sensor(room)` names one member of `Sensor` identified by the string `room`; bound by assignment, its type is the class name. Construction records nothing. Reading a never-written field yields `empty`. A class declared by a unit you compiled earlier is constructed the same way wherever that unit's scripts are called by their bare names (§7.4). Its fields, its methods, a parameter annotated with it and `isinstance` all work as they do for a class your own unit declares. Declaring a class that extends it is refused: a subclass needs the class's body, which only the unit that declared it has. A class, script or import of the same name in your unit takes the name, and a class declared again replaces the earlier one for the units compiled after it. ### 8.3 Field read and write ``` reading = s.temp # read → active_claim or empty remember(s.temp, "21", "op1") # write value + provenance source ``` A read has type `active_claim`; consume with `match` or `.value`. Accessing a field the class does not declare is a `type_error`, as is calling a method it does not declare. Inside a method the **explicit receiver `self`** (the first parameter) names the instance (`self.temp`). Every verb that takes a `K.s.a` cell takes a field written this way as that field's cell, the one `remember` writes, through any name that holds an instance: `current(s.temp)`, `get(s.temp)` and `why(s.temp)` read it, and so do `verify`, `agreement`, `writers`, `pin`, `unpin`, `prefer`, `unprefer`, `why_not`, `what_needs`, `assert`, `forget` and `would_forget`, and a hypothesis takes it as each field it supposes and as the field it reads (§8.9). The cell is that of the class the instance was constructed with, so through a parameter of a base class it is the subclass instance's. A verb that takes a class and a subject — `caveats` and `list_attributes` — takes the instance itself as that subject, `caveats(s)`, through any name that holds one. A verb that takes a class and one of its fields — `list_values`, `list_subjects`, `subjects` or a fold — is given the class, `list_values(Sensor.temp)`. An instance's field at either is a `type_error`. A field is reached through an **instance variable**: a name bound by construction, a parameter of the class's type, a call to a script declared to return the class, the loop variable of a `for` over a list of instances or a tuple of them, an element taken from such a list by index, a second name for one of these, a conditional expression over two instances of classes with a common base, or a binding annotated with the class. A list of instances is a display of them, a name bound to one, a `list` or `*args: Sensor` parameter, a call to a script declared to return `list`, a comprehension that collects its own loop variable over one, a slice of one, or two joined with `+`. An instance in a dict or a tuple whose type is declared, or in a dict or tuple display that holds one, is reached the same way: by key, by a literal index, by unpacking, and through `values()` and `items()`. Through a name holding an instance from anywhere else, such as the value `d.get(key, default)` returns, a verb reaches the field, as above, but reading the field itself through it is a `type_error`, as is a field read straight off such an expression (`xs.pop().n`); declare the name with the class first: ``` class Sensor: temp: string def readings(rooms: dict): x = rooms.get("hall", Sensor("none")) item: Sensor = x reading = item.temp ``` A method called through such a name or straight off such an expression, and a class match over the name (§8.6), are refused the same way, naming the binding to write first. A field or a method may also be taken straight after a call or an index that reaches an instance this way, with no name written between them: `reading = rooms["hall"].temp`, `sensors()[0].record("21", "op1")`. The call or the index is evaluated once, where the expression is. ### 8.4 Void method ``` s.record("21", "op1") # statement position; self is supplied by the receiver ``` `def name(self, params):` — no return type and no `return `; performs actions; called as a statement. The `self` argument is supplied by the receiver, not written at the call. A method's arguments bind as a script's do (§7.1): by position or by keyword, an omitted one takes its default, and a parameter list may hold `/` and a bare `*`. A method's call takes a spread as a script's does (§7.4): `s.record(*pair)`. Python counts the receiver among the positional arguments, and so does a refusal: `Box.record() takes 3 positional arguments but 4 were given`. It names the method by the class whose body holds its `def`, as Python does, so a method `Sub` inherits from `Box` is `Box.record`. Called on a class another module declares, it is named by that class instead. ### 8.5 Value method ``` name = s.label() # expression position ``` `def name(self, params) -> type:` whose body is what a value script's may be, any statements with a `return` of that type on every path; called in expression position. With no `-> type`, a body holding a `return ` gives the method its type, as a `def`'s does. The return type is any a `def` takes, a collection included (`-> list`). A method returning a field read declares `-> active_claim`, or nothing; one returning a store fold declares `-> value`, which its caller narrows with `match`, as its own body may. A method may call one written below it. Resolved from the receiver's class — the actual class when the receiver is held at a base type (§8.10); compile-before-call; no recursion. Called as a statement, `s.label()` runs and its value is discarded, as a value script's is. ### 8.6 Class match (dispatch by class) ``` match s: case Sensor: remember(s.temp, "22", "op1") case Actuator: remember(s.state, "on", "op1") ``` A `match` whose scrutinee is a name the compiler follows to an instance may dispatch over class names. Inside `case :` the instance is treated as that class; a `case` matches that class or any subclass, first match winning (§8.10). An arm names its class as an annotation does, so a class a module declares is `case geo.Site:`, or `case Site:` after `from geo import Site`. Written so, the arm set is closed and **exhaustive** — cover every class the instance could be, or provide a catch-all, `case default:` or `case _:`, which must be the last arm — and a non-exhaustive one is a `parse_error`. `case Site():`, Python's spelling, is the same arm and is a test, as §7.8 reads one over any value: a `match` with an arm written so runs no arm when none holds, and takes `|` between classes. ### 8.7 Computed fields A class field declared with an initializer the engine **derives** from the class's other fields and keeps current as they change. The customer writes only the inputs; the derived field reads back like any field, with a `derived` provenance in place of a source. ``` class Order: price: int qty: int total: int = price * qty # int: an arithmetic expression (+ - * %) over earlier fields big: bool = total > 1000 # bool: one ordered comparison of an earlier field ``` An `int` field is arithmetic (`+ - * %`) over earlier fields; a `real` field may also use `/`; a `complex` field is arithmetic (`+ - * /`) over earlier fields, numbers and imaginary literals, and derives what a program computes for the same expression (§6.3); a `bool` field is one ordered comparison (`< <= > >=`), and holds neither `True` nor `False` for a subject whose input it cannot rank — a blob, which has no order, or a value of another type, such as a string recorded in an `int` field; `why_not` there says so. A complex number has no order either, and a `bool` field that compares a `complex` field is refused where it is declared. A field whose expression divides by zero holds no value for that subject. Every reference is to a field **declared earlier**, so a class's computed fields form no cycle among themselves. One that would close a cycle with what the store already holds, a computed field an earlier compile installed or a rule, is refused at the compile that closes it, naming the field, when the cycle passes through a field that applies an operator (`total: int = price * qty`); a cycle of copies and comparisons settles and is accepted. Writing an input re-derives every field built on it. `why` over a computed field lists the inputs it was derived from (§5.3). A computed field is never written. `remember`, `update` or a supposition there refuses, naming the inputs to write instead, and `current` goes on reading the derived value. `forget` there is never refused. Installing a class whose computed field already holds recorded values refuses as well, naming them: forget them, or give the computed field a name of its own. ### 8.8 Standing rules A named top-level declaration — a sibling of `class` / `def`, **not** a class member — that derives a field automatically: its conclusion is in force exactly while its condition holds, so a derived field always reflects the current data. The `@rule` decorator marks the `def` as a rule; `absent` names one of its three negative premises, **inside a rule block only**. ``` @rule def needs_review(): for o in Order: if o.total > 1000 and absent(o.shipment): o.review = True ``` - `for in (, in )*` — the name the rule gives each subject, and the kind whose fields it reads under that name. The premises say which subjects a name ranges over. A name whose fields a comparison, a join or another ranging premise reads ranges over the subjects recorded with those fields; a name reached only through a join or a relation ranges over the subjects the join or the relation names, whether or not anything of its kind is recorded for them. So in `for e in E, x in N, y in N: if e.a == x and e.b == y: Link.x.y = True`, an `E` naming `p` and `w` concludes `Link.p.w` though no `N` named `w` is recorded; adding `and y.name == y.name` limits `y` to the `N`s recorded with a `name`. Names must be distinct. The bindings are a conjunction and carry no order; a nested `for` is not accepted, because nesting would read as a walk and a rule states a condition over the whole store. - `if (and )*` — every premise must hold: | premise | example | holds when | |---|---|---| | comparison | `o.total > 1000` | the field stands in that order (`< <= > >=`) to the bound. A numeric bound may be negative (`r.temp < -10`) and need not be whole (`o.price > 19.99`) — what is compared is the number, not the way either side was written; a quoted bound (`o.name < "m"`) orders the field alphabetically instead. A bound in scientific notation (`o.mass > scientific("1.0e4")`) is numeric and orders by magnitude. The two readings never mix, so a field carrying both text and numbers never answers by accident. | | equality | `o.lic == "MIT"` | the field holds exactly that value — a quoted string, a number (whole or fractional, negative included), a number in scientific notation (`scientific("1.0e5")`), a complex number (`1+2j`), a blob (`blob("00")`), or `True` / `False`. A blob is opaque bytes and a complex number has no order, so these two literals carry `==` and `!=` and not `< <= > >=`. Over numbers it reads the **number**: `o.total == 5` holds for `5.0`, and not for the text `"5"`. A complex number and a number compare by value, as a program's `==` does: `z == 1+0j` holds for a field holding `1`, and `z == 1` for one holding `1+0j`. | | inequality | `o.total != 5` | the field holds some **recorded** value other than the given one. A field with no value holds neither `==` nor `!=` — comparing against nothing is not a comparison. Ask that with `absent`. | | join | `c.caller == f` | the field holds another subject the rule binds. The right side is one of the `for` names, not a value: that is how a rule relates two subjects to each other. `==` also **ranges** the name on its right, over the subjects the field names, so the conclusion may be about one of them; `!=` does not. The right side is a subject; to compare against a second **field**, write that field — the next row. | | field comparison | `o.delivered > o.deadline` | the two fields' stored values stand in that order — numerically between numbers, chronologically between instants, by length between durations, alphabetically between texts — or are equal / unequal under `==` / `!=`, as a program's `==` decides, so `1+0j` equals `1`. Values of two different classes stand in no order, so a duration against a number does not hold. Both fields must have a value: a subject with no `deadline` has nothing to be late against and is not concluded about. The two may belong to the same subject or to different subjects the same `for` binds (`c.amount > l.cap`). One token separates this from a join — a bare subject on the right is a join, a subject followed by a field is this. A specific subject (`Order.o1.total`) cannot be named on the right; a rule ranges over the subjects it binds, and `current(...)` is what asks about one | | relation | `Reach.x.y == True` | the three-segment path holds that value, with **bound names** in its last two slots. It ranges both names over the pairs it holds, and a bound name in the field position asks for **every** cell of that subject rather than requiring one to be known. | | absence | `absent(o.shipment)` | the subject has **no value recorded** for that field — missing information, not a field recorded as `false` or empty. The stricter of the two cell-negatives: a value a rule concluded makes it false. So does a known absence recorded with `assert(...)`, which is information rather than its lack: `known_absent` asks about that. | | not asserted | `not_asserted(b.flies)` | **nobody has recorded a value** for that field. Narrower than `absent`: it reads only what people wrote, so a value a rule concluded leaves it holding. Shape-only — there is no form taking a value, because *did a person record THIS value* is answered by reading the field. A known absence recorded with `assert(...)` was recorded by a person, so it makes this false too. Ranges no subject, as `absent` does not. | | known absence | `known_absent(p.chip)` | a source **recorded the field as known to be absent**, with `assert(...)`. The positive form beside the two negatives, and the three tell a field's states apart: `absent` holds when it has no value at all, `not_asserted` when nobody recorded one, `known_absent` when a source recorded that there is none. A value does not satisfy it, and neither does a field nobody wrote. It reads a claim, so it ranges the subject. Beside `source(p.chip) == "vet"` it holds only where the vet recorded the absence. | | nonexistence | `none(c in Call: c.caller == f and c.tainted == True)` | **no subject of that kind satisfies every premise inside**. Where `absent` asks about one cell of one subject, this asks about a whole set. The inner premises are the ordinary premise grammar and may name ONE of the outer rule's subject variables — `c.caller == f` is what ties the set to the subject being concluded about — and it is answered for each subject that variable takes, whether or not the rule concludes about it: in a rule over `for r in Req, u in User:`, `none(f in Flag: f.user == u)` asks about each owner `u`. One naming two of them is refused. It holds **vacuously** for a subject the set never mentions: a function with no callees at all satisfies "none of its callees is tainted". That is also why it is enough on its own to say which subjects a rule applies to, where an `absent` is not. A `none(...)` inside another is refused; conclude the inner one into a field with its own rule and quantify over that. | | source | `source(r.temp) == "lab"` | the value came from that source — the text a write gave as its source, quoted. It is about where the value came from, not what it is: a field three sources disagree on holds it through the value that source recorded, and every other premise on the field, and a copy of it, reads that same value. `!=` holds through a value some other source recorded, and does not say the named source did not record it. A value a rule concluded has no source a person gave it and satisfies neither. The right side may instead be a subject the rule binds (`source(r.temp) == s`, `==` only): it holds when the value's source is that subject's name, and ranges the subject over the ones the store holds of its kind. | | trust | `trust(s.label) == "derived"` | the value was concluded by a rule (`"derived"`) or recorded by a person (`"asserted"`) — the two words `.trust` reads back, English on every surface (§12). `!=` selects the other. A field holds both only where a rule yields to a recorded value, so this is what tells the defaults from the values recorded over them. | - premises that name the **same field through the same name** read **one value** of it. A field that two disagreeing sources each hold a value in (§5.2) satisfies `r.t > 30 and r.t < 40` only if one of its values lies in the band: `50` and `20` do not, though each meets one bound. A join names the subject that the value the other premises accepted refers to, a source or trust test is about where that value came from, and a fold over the field folds only the values the premises accepted. `known_absent` accepts the recorded absence, so the premises beside it are about that claim. - each of `absent`, `not_asserted` and `known_absent` also takes a **pair**, written as a relation premise writes one: `absent(Gap.x.y)`, both names bound by the `for`, asks about the cell of `Gap` at the subject `x` and the field `y` names, as the one-field form asks about one cell, and a value recorded there later withdraws what its absence concluded. `absent` and `not_asserted` over a pair range neither name; `known_absent` ranges both. The field is whichever one `y` names, so a rule testing `absent(Gap.x.y)` may not conclude any field of `Gap`; over the same pair, `not_asserted(Gap.x.y)` is the default that yields. - `not_asserted(...)` is the one negative a rule may point at **its own conclusion**, and that is what it is for: a rule states a default and stops concluding the moment a person records something else. **This is the idiom for "everything of this kind, unless…"**, and it needs one thing a reader reaching for it usually leaves out: a **positive premise to range the subjects**. A negative says which subjects a rule does *not* apply to and never which ones it does, so `if not_asserted(b.flies)` alone is rejected — pair it with a premise that names the population, as `b.species == "avian"` does below. ```python @rule def birds_fly(): for b in Bird: if b.species == "avian" and not_asserted(b.flies): b.flies = True ``` Recording `b.flies = False` for one bird stops the rule concluding about that bird — its premise stopped holding, so no conflict is created and nothing has to be ordered. Removing the record returns the default. `absent(b.flies)` there would be self-cancelling and is refused, with a message naming `not_asserted(...)`. - a rule using `none(...)` records an intermediate field on the subject the `none(...)` is answered for — the outer subject variable its inner premises name, or the subject the rule concludes about when they name none — named after the RULE (`__none`, numbered from 1 in the order the `none(...)` premises are written), so two rules concluding one field never share one. A rule `is_safe` concluding `f.safe` records `f.is_safe__none1`; a rule `open_req` concluding `r.open` over `none(f in Flag: f.user == u)` records `u.open_req__none1` on each user. In a module, a field recorded on a kind the module does not declare puts the module's name first: `audit__is_safe__none1`. It is an ordinary derived field — it appears in `list`, in `current`, in `checkup` and in `why` of the field itself; the conclusion is drawn where it is absent, so it is no element of the conclusion's `why` — and it holds `True` for exactly the subjects the `none(...)` excludes. Reading it is a supported way to see which subjects the quantifier found. - the conclusion ` = ` is recorded with a `derived` provenance; the customer never writes it directly — a write there refuses, as it does at a computed field (§8.7), unless the rule **yields**: its condition includes `not_asserted(...)` on the very field it concludes, so a recorded value wins and may be written. A pair conclusion `Pref.x.y = …` yields at every pair when its condition includes `not_asserted(Pref.x.y)` over the same two names. A rule that concludes a field at every subject while also reading it (`p.email = q.email` over one class) leaves no value that could be written there for it to read, and its refusal says so: give the conclusion a field of its own, as `p.email_seen = q.email` does, or let the rule yield. A rule that does not yield is refused if its field already holds recorded values, naming them; forget them, or conclude at another field. Merging a world whose recorded values sit at such a field is refused the same way (§5.9). The literal may be a negative integer (`r.adjustment = -5`), a number in scientific notation (`s.avogadro = scientific("6.02e23")`), a complex number (`s.gain = 1+2j`) or a blob (`d.marker = blob("beef")`); the field then reads back as that kind. A **cell** is either `.`, a field of a subject the `for` clause bound, or the three-segment `..` path used everywhere else in the language — except that its last two segments may be bound names, which is what records a fact about a **pair**. Every name in a conclusion must be bound by the `for` clause. A join or a relation premise binds no name; it ranges one, deciding which subjects the name stands for, and a relation premise ranges both of its names, so either may be the subject of the conclusion: `Back.y.x = True` records `Reach.x.y` backwards. - the conclusion may instead be a **fold** over the subjects the rule ranges over, ` = ()` or ` = (.)` — `c.order_count = count(o)`, `c.total = sum(o.amount)`. See **Folding the subjects** below. - the conclusion may instead **copy** a value the rule reads: a field, ` = .` — `d.owner_name = p.name` — or the value a relation holds for a pair, ` = ..` — `r.dist = Dist.x.y`, whose two names are bound as a relation premise's are. The value recorded is the one read there, of the same kind, once for each value the field holds, and valid while that value is. Reading the field makes its presence a premise and ranges its subject (both names, for a relation), so a default that copies needs no comparison: `if not_asserted(s.label): s.label = s.name`. A path naming a specific subject is not copied, and a conclusion cannot copy what it concludes (`x.a = x.a`, `Km.x.y = Km.x.y`), which would be its own only source. Nor can it copy or fold a field the rule tests with `known_absent`: a recorded absence holds no value to carry, and a rule that concludes the absence writes `assert()` instead. The same field of another subject is how a value travels along a link (`x.name = y.name` with `x.same_as == y`). - the conclusion may instead **record an absence**, `assert()` — `assert(p.chip)`: the field is recorded as known to be absent, as a program records it with `assert(K.s.a, src)` (§5.1). It takes no source, because the rule is the source, and a second argument is refused. `known_absent(...)` holds over it, `absent(...)` does not, and `not_asserted(...)` does, since no person recorded it. Like any conclusion, it goes when the condition stops holding. **Folding the subjects.** The `for` clause ranges over pairs, the conclusion names one of them, and the fold is taken over the other — **grouped by the subject the conclusion is about**: ``` @rule def order_count(): for c in Customer, o in Order: if o.customer == c: c.order_count = count(o) @rule def order_total(): for c in Customer, o in Order: if o.customer == c: c.total = sum(o.amount) ``` A customer with four orders gets `4`, one with a single order gets `1`. **Not an aggregate query.** §5.8 folds a whole attribute across every subject of a kind and answers once: `count(Order.customer)` is how many orders exist, where `count(o)` here is how many are *this customer's*. **Which folds may appear.** Any **single-operand** fold of §5.8 — every family there but *ranked* and *two-attribute*, whose extra argument a conclusion has nowhere to put. Each keeps its §5.8 meaning, the identity and positional folds included: `argmax(o.amount)` records the **subject** at this group's maximum, `distinct(o.region)` a count of distinct values, and `distinct(o)` the number of distinct subjects. **What is folded, and what `count` counts.** The folded name must be one the `for` binds. A subject missing the folded field adds nothing to a fold over it and still counts toward `count(o)`, since naming a field makes its presence a premise. **A fold ranges over the premises' MATCHES**, and a location holding more than one value in force matches once per value (§5.2) — two orders, one of which two sources agree belongs to this customer, make `count(o)` answer `3`. Each fold follows the claims of the field IT reads, so the two shapes move independently: a second claim on the JOINED field raises `count(o)` and leaves `sum(o.amount)` alone, and only a second claim on the AMOUNT adds that amount again. A premise on the folded field decides which of its values count: with `o.amount > 10` in the condition, an amount recorded as `5` by one source and `15` by another folds as `15`. **A derived field is an ordinary premise**, so `if c.order_count > 3` in a second rule is how "more than three orders is a regular" gets written. **What a conclusion may write — a `class` does not constrain it.** A rule concludes into a **path**, and paths are open. Three consequences, all of which surprise a reader who has met `class` first: - A rule may conclude about a **kind no `class` declares**. Nothing has to be declared before a rule writes it. - A rule may conclude into a **field its class does not declare**. Declaring `class Order: total: int` does not close `Order` against a rule concluding `o.review`. - The field's **declared type does not constrain the concluded value**. A rule concluding `s.mean = avg(r.v)` over `int` readings records `1.5` into a field declared `int`, and nothing refuses. This third one is **not** a difference between the two surfaces — it holds on the instance surface too, as the paragraph below says. That is not a contradiction of §8.3, which makes an undeclared field a `type_error` — it is the same rule seen from the other side. **§8.3 governs instance access**: `o = Order("o1")` binds an *instance*, and `o.undeclared` is refused because a class is a closed schema for the instance surface. A rule's `for o in Order` binds a **path**, not an instance, and writing a path is open. A `class` is a schema you get checked against where you construct one, and a vocabulary you are not confined to where you reason. **Which half of the schema closes.** §8.3 closes the instance surface over field **names** and not over the **class of a written value**. `o.undeclared` is a `type_error`; `remember(o.total, "wat", src)` into a field declared `int` is not, on either write spelling, and the value reads back as the `string` it was written. So a typo'd field name inside a declared class is refused and a wrong-classed value in the same statement is not. The declared type governs a computed field's initializer (§8.7), where a non-numeric operand is refused with teaching, and it constrains no write, by any spelling. Nothing is unsound here: `.value` is the union type, so arithmetic on it is refused (§6.3) and a caller destructures with `match`. A conclusion may be `False` as readily as `True` — `o.big = False` records the `bool` `False`, which is a recorded value and not an absence. The `absent` premise in the table above is what tells those two apart. A rule that reads the same relation it writes applies to its own results: ``` @rule def reaches_direct(): for c in Call, x in Fn, y in Fn: if c.caller == x and c.callee == y: Reach.x.y = True @rule def reaches_further(): for c in Call, x in Fn, y in Fn, z in Fn: if Reach.x.y == True and c.caller == y and c.callee == z: Reach.x.z = True ``` The first records every direct edge; the second joins a reach already known to a further edge and records the longer one. It settles on everything the graph connects rather than on a fixed number of steps. A name used twice in one cell **agrees with itself**, so `Reach.x.x` asks which subjects reach themselves: ``` @rule def in_cycle(): for x in Fn: if Reach.x.x == True: x.cyclic = True ``` Every name a rule binds must be ranged by a comparison, a join, or a relation premise — a name mentioned only in an absence test says *which* subjects nowhere — and a rule must not cancel itself out; either is rejected when the rule is defined. It ranges over finitely many subjects; termination is preserved. A derived field reads back like any other (`current` / `get`). At the wire surface, `list(target = rules)` names the standing rules you wrote with `@rule` — names only; keep your own source for what each rule does. It is authored-only: a computed field is a standing rule too, and it is not listed, because it has no name you gave it. So an empty listing means you wrote no `@rule`, not that nothing is reasoning — a class declaring `area: int = w * h` and no `@rule` lists nothing here and reports `rules=1` from `checkup`, which counts every standing rule in force. The two verbs answer different questions and the difference between them is the computed fields. `forget_rule(name)` removes ONE standing rule and reclaims what it derived, leaving the rest of its module in place; `name` is the qualified `module.rule` name `list(target = rules)` prints. A conclusion another standing rule still supports is re-derived and stays — only this rule's support is withdrawn — and the facts the rule *read* are untouched. Wire-only; needs a `read-write-delete` key. `checkup` answers the question a silent rule leaves open. A rule that never fired and a rule that fired and found nothing return the same empty result, and when the reasoning came from a module you did not write, nothing else tells you which happened. It reports `unmet .` for a cell some rule or constraint needs a value at that holds nothing (a premise asking for an absence, such as `absent(...)`, is met by an empty cell, so it is never reported) — `no-such-kind` when nothing of that shape exists, `no-values` when the kind is there and the field is empty, and `not-in-force` when a value is recorded at the field but its validity window does not cover the time asked about, which is a fact you already hold rather than one to write — `unread ` for a kind of yours no rule or constraint reads, `violated …` for a standing constraint (§8.12) that does not hold, `near A ~ B` when those two lists contain names a letter or two apart, and a counts line. A `near` line is usually a mis-spelled kind name: writing facts under a kind spelled slightly differently from the one the reasoning reads produces both halves, and the pair names the mistake in a way neither half does alone. Findings name cells rather than rules — what an unmet cell needs is your data. A `violated` line is the exception that proves it: a constraint is a named invariant you declared, so the constraint is the right noun, and the subjects beside it are what make the finding actionable. The count is exact; at most five subjects are listed. The counts line's `rules=` covers **every standing rule in force**: the rules you wrote with `@rule` and the computed fields your classes declare. A computed field is counted for the reason this verb exists — it fires, it derives, and when a premise of it goes unmet it stops concluding silently. `list(target = rules)` is authored-only, so `rules=` is at least as large as that listing, and the difference between the two is exactly the computed fields. A class declaring `area: int = w * h` and one `@rule` lists one name and counts two. Wire-only; takes no arguments and changes nothing. **A rule's kind.** `@rule(kind="…")` says what kind of relation a rule states, one of five: `"definition"`, `"causal"`, `"empirical"`, `"policy"` or `"heuristic"`. It is for the reader and changes nothing the rule concludes: the same rule with and without a kind concludes the same claims. A conclusion reads it as `.rule_kind` (§11.2), `list(target = rules)` shows it after the rule's name (`shop.needs_review (policy)`), and so does `info_module`. The five words are the same in every language; the keyword is spelled in the unit's language. A recompile that changes the kind changes it, and one that leaves it out removes it. `@rule` takes no other argument. ``` @rule(kind="policy") def needs_review(): for o in Order: if o.total > 1000: o.review = True ``` ### 8.9 Hypothesis `suppose(, , …, )` reads what the **last** field would be if each named field were set to the value beside it — letting standing rules and computed fields run under the suppositions — then discards everything. Nothing persists. Returns a `value`; bind it and read it, or concatenate it into a `print`. ``` would = suppose(Order.o1.total, 2000, Order.o1.review) # true, if a rule sets review over 1000 ``` Give a `, ` pair per supposed fact. The field to read is always the last argument, alone — so a rule needing two facts at once can be asked about: ``` would = suppose(Order.o1.total, 2000, Order.o1.shipment, "sent", Order.o1.review) ``` Suppositions apply **left to right**: two naming the same field leave the later one standing, exactly as a later write wins over an earlier one. A hypothesis takes **1 to 16** of them — a cost bound rather than a semantic one, since the work grows faster than the count; over the limit is a compile error naming the number. If the read field has no value under the suppositions, `suppose` produces no value — a catchable `refuse` (guard it with `try` / `except`). Safe for preview, comparison, and what-if exploration; termination preserved. `suppose_absent(, …, )` asks the opposite: what the last field would be if the other fields' facts **did not exist**. Same isolation — the suppositions and everything that follows from them are discarded, and nothing is removed. It takes 1 to 16 fields to suppose away, applied left to right, and the read is the last argument — so `suppose_absent(a, b, c)` supposes away `a` and `b`, and reads `c`. ``` r = suppose_absent(Order.o1.total, Order.o1.review) match r: case active_claim: print("review would still say", r.value) case empty: print("review would disappear") ``` Returns an **active claim that may be empty**, matched exactly like `current` — not a `value`. The two hypothesis verbs differ here on purpose: `suppose` asks what a value *would be*, so a missing answer is an edge case and arrives as a catchable `refuse`; `suppose_absent` asks what would *survive*, and "nothing" is the answer it exists to give, so it arrives as an ordinary empty read. A single hypothesis supposes facts **into place** or **away**, not both; mixing the two in one call is not available. Every field either verb names is a `K.s.a` cell written as any verb's is (§5): a segment held by a `string` name in scope is that name's value, and an instance's field, `s.temp`, is that instance's cell (§8.3). A field that is no path, or goes on past one as an expression, as in `suppose(Order.o1.total + 1, 2000, Order.o1.review)`, is refused at it, as a verb's path argument is. Use it before `forget` (§5.1), which cannot be undone. `dependents` tells you what points at a fact; this tells you what the answers become without it — while the fact is still there to keep. Termination is preserved. ### 8.10 Inheritance A class may **extend** one base class, named in parentheses; it inherits the base's fields and methods and may add its own or **override** a method: ``` class Animal: sound: string def speak(self): remember(self.sound, "generic", "op1") class Dog(Animal): # single inheritance (one base) breed: string def speak(self): super().speak() # run the base's version, then extend it remember(self.breed, "collie", "op1") ``` Single inheritance only (`class C(A, B):` is an error), and `class A():` with empty parentheses names no base, the same class as `class A:`. `super().(...)` runs the base's version on the same instance; when the method returns a value, the call is that value (`return "z" + super().label()`). `super()` names the base of the class declaring the method, also in a subclass inheriting it. A subclass instance is accepted wherever the base is expected — a base-typed parameter, a binding, a list element, or the value a script or method declared `-> Base` returns — and a class that neither is the named one nor descends from it is refused where the name is written, when the program compiles rather than when it runs. A list **written** with several subclasses takes their nearest common base as its element type, so `[Dog(d), Cat(c)]` is a `list`. A list written where a type is declared for it (a binding, an argument, a `return`, a default) takes that type, so `[Dog(d)]` passes where a `list` is named. A dict written with values of several subclasses takes their common base as its value type in the same way (`{"d": Dog(d), "c": Cat(c)}` is a `dict`), and so does a display written inside a display, at any depth (`[[Dog(d)], [Cat(c)]]` is a `list>`). Instances of classes that share no base may be held together, but join under no class: a method call through one of them runs the method of the instance's own class, and a field read through one is a `type_error`. A list a name already holds does not widen, since the list is one object its holders share (§6.7). A method call through a base-typed reference runs the **actual** class's method (`for a in animals: a.speak()` runs each element's own version); value methods dispatch the same way. Where the method of the instance's class returns nothing, the call runs it and reading its value is then refused as the line runs (``method `B.m` returns nothing — it cannot be used as a value``), since CPython's answer is `None`; no `except` takes the refusal. So does a read in a method a subclass inherits, and in a script written above the class. A call whose arguments the method of the instance's class does not take is refused as it reaches that class, in the words of CPython's `TypeError` (``B.m() takes 1 positional argument but 2 were given``), and `except refuse` takes it. A read no class can answer, and a call no class's method takes, are refused when the program compiles. Through a base-typed reference you may use any member the base declares; a subclass-only member needs `isinstance` (§8.11) or a class match (§8.6) first. The class set is closed and finite, so dispatch is bounded and termination preserved. ### 8.11 `isinstance` `isinstance(x, C)` → `bool`: **true** when `x`'s class is `C` or a subclass of `C`. Inside the taken branch it narrows `x` to `C`, exposing that class's members; the test must be the whole condition, the narrowing ends with the branch, and a tuple or union narrows to the nearest class its alternatives share (sharing none, or naming a type, narrows nothing). The second argument may instead name a type as the language spells it, or `str` / `float` for `string` / `real`, and then tests any value on what it holds when the call runs: `isinstance(1, int)` and `isinstance(True, int)` are `True`, and a tuple or `|` union of classes and types is true when any is — `isinstance(x, (int, real))`, `isinstance(x, int | real)`. A value no class holds is `False` for a class, `isinstance(1, Dog)`, and every value is an `object`. A name holding a `value` is narrowed by a test against types, to the tested class: in the taken branch, after the test in an `and`, after a comprehension's `if`, in a conditional's first branch, and in the body of a `match` arm whose guard makes it. `int` beside `bool` narrows to `int`, either beside `real` to a `value` holding a number, and an `or` of two tests to what they join to. A field read such as `c.value` is not narrowed. A `value` built from scalars holds their classes, `[1, "a"]` an `int` or a `string`, and where a test of a name holding one fails, the name is read at the classes left to it: in the `else`, in a negated test's branch, in a conditional's second branch, after the test in an `or`, and in a `match` arm below arms naming classes. A claim's value may be any class, so a failed test leaves it a `value`, as does the `else` of an `and`. A branch no `value` can take, such as one `isinstance(v, list)` guards, is never run and not read, and an operator meeting a `value` of known classes runs where it takes each: `x * 2` over an `int`, a `real` or a `string`. ``` if isinstance(d, Dog): print("a dog") ``` `issubclass(A, B)` → `bool`: **true** when the class `A` is `B` or a subclass of it, at any depth, so `issubclass(Dog, Dog)` holds as `issubclass(Dog, Animal)` does. Either argument may name a type as `isinstance`'s second does; a type is a subclass only of itself and of `object`, except that `issubclass(bool, int)` is `True`, and a class is a subclass of no other type but `object`, and a type of no class. The second argument may be a tuple or a `|` union, true when any one is. A class is not a value, so each argument is a name written at the call, the first naming one class or type, and the answer is fixed where the call is written. To ask about an instance, use `isinstance`. --- ### 8.12 Standing constraints `@constraint` declares an invariant the store is watched against. It concludes nothing, writes nothing, and never fires: ``` @constraint def stock_never_negative(): for p in Product: if p.stock < 0: violated ``` It reads like a rule with `violated` where the conclusion would be, and that is what it is: the same `for` and `if`, and no head. `violated` is a statement rather than a value, and a reserved word only in that position. Ask for it with `checkup` (§8.8), which reports one `violated` line per constraint that does not hold, naming the breaching subjects. It is never checked on write — recording a fact that breaches an invariant is not refused, because a store mid-edit is routinely inconsistent, and a write that fails on someone else's invariant is a worse failure than a report saying so. **Why not just write a rule?** A rule that concludes `p.invalid = True` records a fact ABOUT the store INSIDE the store, and the next rule reads it as an observation. A constraint has no conclusion to record, so there is nothing for a later rule to pick up. That is the whole difference. A constraint binds **exactly one subject variable**, and its premises are a comparison against a literal (`p.stock < 0`, `p.tier == "gold"`) or an absence (`absent(o.customer)`), joined by `and`. Premises on one field hold through one value of it, as a rule's do. A premise that orders a string field against a quoted bound (`<`, `<=`, `>`, `>=`) is judged by codepoint order, alone or with `absent()`, and a constraint with one is reported violated exactly when a `@rule` with the same premise would conclude; before, such a constraint held silently: ``` @constraint def every_order_has_a_customer(): for o in Order: if absent(o.customer): violated ``` Both limits have one cause: a rule is answered by the reasoning, and a constraint is answered by re-reading your store, so it can ask what a read can ask. To watch something outside that, conclude it into a field with a `@rule` and write the constraint over that field. A constraint shares the unit's one name space with scripts, rules and classes, and `list(target = rules)` does not list one — it derives nothing, so a caller enumerating rules to reason about what your store concludes would be misled by it. **Removing one.** `forget_constraint(name)` drops a single constraint, addressed by the qualified name `checkup` prints. Nothing is reclaimed — a constraint concludes nothing, so the data it watched is untouched, including the subjects that breached it. Stopping the watch is not fixing the breach. `forget_module` removes a module's constraints along with the rest of it; `forget_constraint` drops one invariant and leaves everything else the module brought. ## 9. Modules A `.dpy` source file is a **module** — the unit you submit, store and import. Modules introduce no new types (§4) and no new verbs (§5): a module body is written in the language already described here, and compiling or importing a module is the operation that runs that body. ### 9.1 A `.dpy` file is a module A module's top level is its **body** — a sequence of declarations and statements, read top to bottom. A body may contain, in any order: - **declarations** — `def` (a callable script, §7), `class` (a kind schema, including computed fields, §8), a `@rule`-marked `def` (a standing rule, §8); - **data declarations** — the write verbs `remember` / `update` / `forget` / `assert`, each naming its source as its provenance, written at module scope (§9.2); - **statements** — anything legal inside a `def` body (§7): bounded `for` / `if` / `match` / `try`, reads bound by assignment, `print`, and `commit` / `rollback`; - **`import`** statements (§9.3). The body runs **once, top to bottom, in source order, when the module is compiled or imported**. A `def` becomes callable afterward; a `class` installs its schema and any computed fields; a `@rule`-marked `def` installs a standing rule; a data declaration performs its write; an ordinary statement runs. **A plain call is one of those statements**, and it is how a module demonstrates itself: define a script and then call it, so importing the module both installs the script and runs it once. ``` def greet(): remember(Greeting.g1.text, "hello", "setup") print("greet ran") greet() ``` Importing that module reports the script installed and the body's write and print as having happened. The script stays callable afterward: the top-level call is one run, not the only one. A module with **no `def`** is valid — one that only installs a schema, defines a rule, or writes data declares no callable script and is loaded for its body's effect. A **single-`def`** source is a one-declaration module. **Termination is unchanged.** Every construct legal at module scope is already bounded — there is no `while`, `for` iterates only finite collections, and a script cannot recurse — so a module body always terminates, for the same reason a `def` body does. Module scope introduces no new iteration and no new recursion. **A body that fails keeps nothing.** When a failure nothing catches stops the body — a refusal, or an operation that cannot be carried out, such as an `import` of a module that is not stored (§9.4) — the compile or import that ran it keeps none of the body's work. A compile installs none of the module's declarations, and the writes the body made before the failure are discarded. The store is as it was, so a corrected source compiles with nothing to forget first. What the body printed before the failure is still reported, because it ran. ### 9.2 Data declarations at module scope A write statement written at module scope — not inside a `def` — is a **data declaration**: an assertion of what is, or for `forget` a removal, applied when the module is compiled or imported. ``` remember(Rate.vat.pct, 25, "finance-act-2026") update(Rate.vat.pct, 24, "finance-act-2027") forget(Flag.legacy.enabled) ``` A data declaration is an ordinary write (§5) that happens to sit at module scope; it obeys every rule a write inside a `def` obeys, save the one a re-run changes (§9.5). ### 9.3 `import` ``` import ``` Loads a module already stored under ``: it makes that module's names available (§9.6) and runs its body once (§9.1). `` resolves along the search path of §9.4. This is the one module operation written **in a program body** — a module may import another module. `import` is a reserved word, and begins the statement at statement head. Because loading runs the module body, what that body prints and writes belongs to the run that imported it: the imported body's output and store effects join the importing program's transcript at the point of the `import`, and count in its verdict. An `import` has two effects, and they follow two different rules: - **Its names are in scope from the `import` to the end of the unit**, whatever block the `import` sits in and whether or not that block ever runs. Position decides, not the block: a name used above its `import` is refused when the program is compiled, and one used below it resolves even when the `import` sits in an `if` that never runs, or in another script of the same unit. - **Its body runs when control reaches the `import`, and each time it does** — never, for an `import` in a branch not taken; once for each pass, for one in a loop (each re-run reconciles, §9.5). So `if :` followed by an `import m` does not make `m` optional: `m.f(…)` resolves on every path, while what the body of `m` writes lands only on the path that reached the `import`. This differs from Python, where an import inside a function binds a name local to that function. Loading order is an acyclic dependency order — a module is loaded before a module that imports it — and an `import` **cycle**, a module that imports itself directly or through another, is **rejected when the module is loaded**. **A module your store does not hold.** An `import` naming one fails when control reaches it, with `no module named ` at the `import`'s line, and what ran before it stands. A program that also *uses* that module — calls one of its scripts, constructs one of its classes or names one as a parameter type or in `isinstance`, or uses a name `from` bound — is refused before it runs instead, at that `import` and in the same words, since nothing below the use can be checked without the module. So is a program with a `type_error` anywhere below a top-level `import` of such a module: control always reaches that `import`, so nothing below it could run. A grammar error is reported where it is, above a use of the module or below it (§10). Any other `parse_error` below a use that names one of the module's classes is not reached, since that use is refused as it is read. A script missing from a module your store *does* hold is refused at the call: `module 'm' has no attribute 'f'`. ``` from import , from import (, ) ``` Loads `` exactly as `import ` does, including running its body each time control reaches the statement, and binds each named script or class, so a later `area(2, 3)` means `geo.area(2, 3)` and compiles to the same program. Each name must be one the module declares; one it does not is refused at the import, used or not. Only the names are bound, as in Python: `geo.hello(…)` needs an `import geo` of its own. A bound name holds one thing, so assigning to it, taking it as a parameter or a `for` target, declaring a script or class under it, or importing it from another module with `from` is refused. Keep both with `import ` and the qualified name. Naming it in an annotation, as in `item: Site = b`, uses it and binds nothing. A message that names a bound script or class names it as you bound it, `Site` rather than `geo.Site`, so a form it offers compiles as written. A keyword argument named like an imported name is the callee's parameter, and a class field or method named like one belongs to its class; neither is affected. `import a, b` loads each module in turn. `import geo as g` binds `g` to the module, and `from geo import area as ar` binds `ar` to the name, each as if the module's name had been written; neither binds `geo`, as in Python. A name an import bound is refused if it is bound again to something else. There is no `from m import *` and no relative import. Each is refused naming the form to write. `from __future__ import ` loads nothing and binds nothing: Python's features are accepted and change nothing here. Below the top of a unit, or naming a feature Python does not know, it is refused in Python's words. ### 9.4 Name resolution and shadowing `import ` resolves `` against **the modules stored in your store**, each put there by an earlier `compile` (§5.7). There is no second place to look and no fall-through: a name that is not in your store is not found. The exceptions are the language's own modules — `math` and `cmath` (§6.14), `vector` (§6.15) and `struct` (§6.16) — which are never looked up in your store; no module you compile may take any of those names. This is what makes a published module's name a *default* rather than a reserved word. A module we publish, such as `code`, is a file you compiled into your own store, so `import code` resolves to whatever you compiled under that name — the copy you took from us, or your own edit of it, or something unrelated. ### 9.5 Re-submitting reconciles Compiling a module whose name is already stored — or importing an already-loaded module — **re-runs its body**, and the re-run is a **reconciliation**: - a data declaration whose value **changed** since the last run replaces the claim it recorded before — its own claim and no other: a value another source holds at that cell stays beside it, as any second source's does; - a data declaration whose value is **unchanged** leaves the cell as it is — an identical write is not recorded again; - a `class` or rule install is likewise a no-op when nothing changed; - the body writes each cell **once** per source and period. A second `remember` or `update` of one cell from one source, over a window that overlaps the first, is refused as a conflicting write (`conflicting_write`) naming the cell: replayed on every import, it would revise the cell again each time. Write the final value once. One source writing a cell for periods that do not overlap states two values, and a `def` the body calls writes as any `def` does; - a `def`, rule, computed field or constraint that an earlier compile installed and the text **no longer declares** is removed, and a body the text no longer has stops running on `import`. The answer names each thing it removed. Removing a rule or a computed field withdraws what it concluded, as `forget_rule` does. Removing needs a `read-write-delete` key, as `forget_rule` does: under a `read-write` key a compile that would remove any of these is refused with `scope_forbidden`, names them, and changes nothing. A body the text drops is not one of them, and stops running under either key. Reconciliation leaves the store reflecting the module's current text, rewriting only the cells whose values changed, and is safe to repeat. So the way to stop using one of a module's rules is to delete it from your copy of the file and compile again, and the way to bring it back is to restore the line and compile again. **Data is never removed by a re-import.** A fact the module's body wrote, or one written under a `class` the text no longer declares, stays where it is and stays readable as `module.Kind`. Facts are the one thing compiling again cannot bring back, so removing them takes a verb that says so: `forget`, or `forget_module` for the whole module. This is the one place `import` differs from a language whose `import` runs a module body only once: a DKE Python module is durable store state, not process memory, so re-running re-applies it. Re-importing a changed module reaches the module, and not what was compiled against it. A changed rule or computed field applies at once, since both belong to the module. A changed `def` replaces the module's own script, but a script compiled **earlier** that calls it keeps the body it was compiled with (§9.6). So one re-import can move a module's rules while the callers of its scripts stay where they were — a threshold visibly changes in the same run where an already-compiled caller still answers from the old script. The import's answer names each such script and the module script whose old body it keeps. Compile or import that caller again to move it onto the new version. ### 9.6 Namespacing A module is a **namespace**. A name it introduces — a kind declared with `class`, a script declared with `def` — is addressed under the module name with a dot: `code.Function`, `code.impact_of`. Two modules may each declare a `Function` without collision; they are `code.Function` and `finance.Function`, distinct. Within a module's own body, its own names are reachable unqualified. **An `import` brings the imported module's kinds into scope unqualified.** After `import code`, a bare `Call` in that unit means `code.Call`. A bare kind name resolves in this order: 1. **a kind the unit declares itself wins** — a unit's own `class Call` is never redirected to an imported module's `Call`; local beats imported; 2. otherwise, **the one imported module that declares it**; 3. if **two or more** imported modules declare it and the unit declares none, the program is **refused**, with a `type_error` naming the candidates — import order states dependency, not precedence, so the ambiguity is reported rather than resolved by position; 4. otherwise the name is unchanged — a kind no imported module declares is an ordinary global kind. That holds where a module the unit does not import declares a kind of the same name: the bare name is a kind of its own, beside the module's. A write to it is accepted, and `compile` and `run` answer it with a **note** naming the module and the `import` that reaches its kind, once per kind, at the first write. A read is not noted. The qualified form is always available and always unambiguous, so it is both the way to reach a specific module's kind past a local declaration of the same name and the fix for case 3. The qualified form names only a kind the module declares with `class`. Where `code` declares no class `Pk`, `code.Pk` is refused with a `type_error`, whether it is written to or read: `Pk` is a global kind (case 4), its bare name reaches it, and a rule `code` installs over `Pk` reads it there. The **module-qualified construction** is the module name, the kind and the subject in parentheses. It is an instance of the module's kind, and you read a cell through its field: ``` a = code.Function("f1").fanin # code's Function f1, field fanin print("fanin = " + str(a.value)) ``` This reads like any field read (§8) and its result is an `active_claim`. A field `code`'s `Function` does not declare is refused, read or written, as one your own class does not declare is (§8.3). The parenthesised subject is what distinguishes the construction from an ordinary dotted path (§7). Because each module's names are addressed under its own module name, two modules that both declare a `Function` never read or write each other's cells. The construction is also a value on its own. `f = code.Function("f1")` binds the instance, and then: - `f.fanin` reads the same cell; - `remember(f.fanin, …)` writes it; - `print(f)` shows ``; - `f` can be passed to one of `code`'s scripts whose parameter is a `Function`; - a method of `Function` can be called on it, and `isinstance(f, code.Function)` tests it. A parameter of your own may be annotated `code.Function`. It accepts an instance of `code`'s `Function` or of a subclass `code` declares, and a method called on it runs the method of the instance's own class, as for a class your unit declares. `code.Function` is a type wherever a class of your own is one, so an annotated binding (`f: code.Function = …`), a collection's element (`list`) and a return type (`-> code.Function`) may name it too. **A module's class is constructed only through its module.** After `import code`, a bare `Function("f1")` is refused with a `type_error` that names `code.Function(…)`, just as a bare name is not in scope after `import code` in Python. The unqualified-kind rule above covers a kind in a path, not a construction. If you compiled a script of your own named `Function` earlier, a bare `Function(…)` still calls it. A **script** a module declares with `def` is likewise addressed `module.script` — the name a program calls after importing its module, the name `info` and `forget` take, and the name `list` reports. Two modules may each declare a `restock` without collision. An imported module's scripts are callable from your own code under that same qualified name: ``` import code def review(fn: string): who = code.callers_of(fn) # a value call print(str(who.length) + " caller(s)") reach = code.impact_of(fn) # its result is an ordinary list for r in reach: print(" reaches " + r) code.log_review(fn) # a void call, as a statement ``` Such a call obeys the same rules as any other (§7): arguments are checked against the callee's declared parameters, a value call must name a value script, and the callee must already be stored — which an imported module's scripts are, since importing it stored them. `code.callers_of("f")` and `code.Function("f")` open with the same words. What tells them apart is what `code` declares. `Function` is a class, so `code.Function("f")` is a **construction**. `callers_of` is a script, so `code.callers_of("f")` is a **call**. A call is resolved **when the calling module is compiled**, and the callee's body at that moment is the body the call runs. Importing a newer version of the callee's module afterwards does not change a caller that was already compiled — it changes what `module.script` runs when called directly, and what the next compile of the caller picks up. Import the calling module again to move it onto the new version. ### 9.7 Published modules A **published module** is an ordinary module — classes, rules and queries in one `.dpy` file — that we have written and published for you to use instead of writing the same declarations again. You download the file and `compile` it into your store, once. Nothing about it is privileged: it is the same kind of file you may write yourself, it resolves the same way (§9.4), and once compiled your store cannot tell the two apart. Three things follow, and each is a consequence of the file being yours. 1. **You can read it before you run it.** It is source in this language. What it declares is what its lines say; its published documentation — for the modules we publish, the [Library Reference](/ref/dke/library/) — is a lookup over that source, not a substitute for it. 2. **Its interface is enumerable once compiled.** `list_modules` reports it, `info_module` reports the scripts it defines and the classes it declares, `list` reports its scripts with their signatures, and `list(target = rules)` its standing rules by name. This is a guarantee, not an artefact: a script that could not be discovered could not be called. A module's SOURCE is not stored — the store keeps the compiled form, and there is no readback verb for any module — so the file you downloaded is the copy to keep. 3. **Removal and editing are ordinary.** `forget_rule` removes one of its rules and `forget_module` removes the whole module; nothing reinstates either. To reason without one of its rules, remove the rule, or delete the line and compile again (§9.5). --- ## 10. Errors Two static diagnostic kinds; runtime adds two catchable outcomes. | Category | Stage | Recoverable in source? | |----------|-------|------------------------| | parse (`parse_error`) | static — token structure | no | | type (`type_error`) | static — semantic rule | no | | `refuse` | runtime — verb call | yes, via `except refuse as` | | `engine_error` | runtime — service layer | yes, via `except engine_error as` | Common triggers: - **parse** — tab in indentation, a bad dedent or an indent where no block opens; unterminated string; a closing bracket that closes none, or one of another kind; a bracket never closed; reserved word (`class`, `return`, …) as an identifier; an empty module (whitespace and comments only). - **type** — a name nothing binds; a wire-only script verb in a program body; a non-exhaustive `match`; a `for` over a non-collection; a type mismatch; a non-existent field. - **`refuse`** — the store rejects a well-formed request: out-of-surface slot, wrong slot count, index out of bounds, division / modulo by zero, integer overflow, `commit` outside a branch. Also a service budget on how much one run may build — see below. - **`engine_error`** — a well-formed request the service could not carry out. **A grammar error is answered first.** A grammar error is a `)`, `:` or line break that is missing, or an expression that is not there. So is one of Python's own keywords written where a name or a value goes, as in `y = def`: DKE Python teaches it as a reserved word, and answers it where Python does. So is one written after a `.`, as in `xs.if` or `remember(K.s.class, 1, "op")`: such a word names no field, method or path segment, and a kind, subject or attribute spelled as one is reached through a name that holds it, `k = "class"`, then `K.s.k`. So are two values side by side inside brackets, as in `[1 2]`: DKE Python answers them where the first begins and names the comma that may be missing, as Python does. Python 3.11 answers some pairs at the second value instead, among them one whose first value begins with a name it mistakes for the soft keyword `case` or `match`, such as `c` or `ma`, and DKE Python answers those where Python does. A grammar error is placed where Python places it, which is sometimes a token further on: `x = a.-b` is answered at the `-`, after a `.` no name follows; a `not` no `in` follows is answered at the token after it; `f(a.b=1)` is answered at the `a`, as an argument that assigns to an expression, and so is `if x = 1:` at the `x` and `[1, (a)=1]` at the `a`, wherever Python reads a named expression; a `:=` is answered where Python answers it, at the `:=` in `y = a := 1` and inside its value in `y = (a := 1 +)`, and where its target begins when that is no name, as in `y = (a.b := 1)`; a second `=` after a keyword argument's value, as in `f(a=b=1)`, is answered at that `=`; a dict key with no `:` after it, as in `{1: 2, a.bc}`, is answered at the key's last character, and one whose first operand does not parse, as in `{1: 2, (3 +)}`, at its grammar error; a positional argument after a keyword argument, as in `f(a=1, b, c=2)`, is answered at the call's `)`, once Python has read the rest of the call, and so it is in a verb's call, a builtin's and a class's bases; and a return annotation that ends before its `:`, as in `def f() -> int + -:`, is answered where the expression Python reads ends, as the `:` that is missing. Python reads a whole program before it runs any of it and answers a `SyntaxError` wherever it stands, and so does DKE Python: a program refused for something else, such as `isinstance(x, 5)`, is answered with a grammar error on a later line instead. A refusal made as the source is read, such as a bytes literal, does not hide a grammar error, even one later on its own line. Below a refusal inside a `@rule` or `@constraint`, and later on the line of a refusal inside a value such as `isinstance(x, 5)`, a grammar error is reached. Inside a `@rule`, the rule's own grammar decides what a grammar error is, so a conclusion that is not a value is one. A grammar error is reached later on the line of a refusal in a header, such as a type annotation, a parameter or a method's name, in a decorator or in a `lambda`, and later in a rule's line than its refusal. A starred argument DKE Python refuses is read past as Python reads it, so a grammar error later in its bracket is reached too, as in `f(*a, 1 +)` (an element's `*` is a spread, §6.13, and is read as one, and so is the `*` before an argument of `print`, of a script's call, §7.4, or of a method's, §8.4); a starred value alone in parentheses, as in `y = (*a)`, is itself Python's grammar error at the star. So is one later in a verb call refused for a keyword argument or a second argument it does not take, as in `pin(K.s.a, b, 1 +)`, and so is one after an argument a verb or a builtin refuses for any other reason, as in `isinstance(1, 5, 1 +)`, or after an argument more than a call takes, as in `len(x, c, 1 +)`, or in or after a keyword argument to an aggregate query, which takes none, as in `avg(K.c, key=1 +)`, or in or after a verb's path argument that goes on past its path, as in `current(K.s.a +, t)`, or that is no path, as in `current("s", 1 +)`, and so is one in or after a hypothesis's field that is either. So is one after or below a call short of an argument it takes, as in `remember(K.s.a)` above `z = 1 +`. A grammar error is reached inside the brackets after any refused value, as in `datetime("bad")[1 +]`, and in the arguments of a method call refused at its name, as in `s.foo(1 +)`. A statement refused at its first word is read as Python reads it, its body included, as in `while True: x +`. So is a `class` inside a block or a class body, with its bases and its body. What a decorator refused at its name decorates is read as Python reads it, unless it reads as a rule or a constraint, whose lines may be a rule's grammar under a misspelled `@rule`. A parameter list or a `lambda`'s parameters Python would not read are answered where Python answers them, as in `def f(**kw, y: int = 1 +):`, answered at the `y` after `**kw`. An import refused past its first word, as at the dot of `import m.n o`, is read on as Python reads an import, so that line is answered at the `o`. A malformed escape, or a character such as `$` or `?` that no token begins with, is answered where it stands, so a grammar error above it comes first. A string the source never closes, a malformed number, or a character outside ASCII is answered before any grammar error. **A keyword argument named twice in one call**, as in `f(a=1, a=2)`, is refused at its second name, in Python's words: "keyword argument repeated: a". Python finds it only once it has read the whole program, so a grammar error anywhere comes first. A verb's keyword named twice, as `known_at=` in `current`, is refused at the same place in words of its own. **What Python's compiler refuses**, a program its grammar reads, is refused here at the place Python answers and in its words: a `return` or a `yield` outside a function, a `break` or a `continue` outside a loop, a parameter named twice, a `global` below a use of its name, and others. Its order holds too. A grammar error anywhere comes first, every other refusal comes after, and of two it would both make the one it reaches first is answered, which need not be the one written first. `__debug__` reads as `True`, and nothing may bind it or delete it. **A Python builtin DKE Python does not have** is a type error that names it as one and says why. It is not taken for a script you have yet to declare, and declaring a script of that name would not give you the builtin. Each is in one of four families: | Family | Builtins | Why | |---|---|---| | Left out on purpose | `aiter` `anext` `bytearray` `bytes` `callable` `classmethod` `eval` `exec` `filter` `globals` `id` `locals` `map` `property` `staticmethod` `vars` | each belongs to a Python feature this language leaves out: asynchronous iteration (`aiter`, `anext`); bytes, which are a `blob` here (`bytes`, `bytearray`); functions held as values (`callable`, `filter`, `map` — a comprehension filters and maps); method decorators (`classmethod`, `property`, `staticmethod`); running text as code (`eval`, `exec`); namespaces held as values (`globals`, `locals`, `vars`); and object identity (`id`) | | Out of reach | `breakpoint` `delattr` `dir` `getattr` `hasattr` `hash` `input` `memoryview` `object` `open` `setattr` `super` `type` | each reaches the interpreter or the machine, and a program here reads and writes a store | | Needs a value this language does not hold | `frozenset` | a set that cannot change | | Undecided | none | no builtin is undecided | `super` is in the table only outside a method. Inside one, `super()` reaches the base class (§8.10). **A service budget is not catchable.** A service may limit how much data a single run builds. This is separate from the language's own guarantees: bounded iteration bounds how LONG a program runs, not how much it HOLDS at once, so a program can terminate as promised and still build more than the service will hold. Such a limit is reported as a refuse but `except refuse` does not catch it — a handler able to swallow a resource limit could retry inside a bounded loop and defeat it. Build fewer values in one run, or split the work across runs. --- ## 11. Accessors and field tables ### 11.1 Universal accessors - `.kind → string` — for any discriminator and for `proof_tree`. - `.length → int` — for any `string` (its character count — Unicode characters, not bytes) or finite collection (its element count). ### 11.2 Field access by type | Type | Field | Field type | |------|-------|------------| | `active_claim` | `value` | `value` (union; §4.1) | | `active_claim` | `source` | `string` | | `active_claim` | `path` | `string` (cell path `K.s.a`, or `m.K.s.a` for a kind module `m` declares; the cell a `dependents`/`premises`/`conflicts` claim lives at) | | `active_claim` | `claim_id` | `string` — the claim's name: its cell path, `#` and a code of twelve hexadecimal digits (`Cat.felix.color#3f09c1a27b44`), made from what the claim shows (its value and type, where it came from, its window, its qualifiers and `created_at`). The same claim carries the same `claim_id` on every read, and writing, revising or removing another claim leaves it unchanged. A claim recorded anew has one of its own: the new claim of a revision, a conclusion a rule draws again, a value in force the `restore` tool brings back (an older version it puts back keeps its own). Two claims at one cell that agree on all of those fields share one. `dependents`, `premises`, `forget` and `would_forget` take it in place of the claim (§5.3) | | `active_claim` | `created_at` | `string` (RFC 3339 UTC µs; empty when the time the claim was recorded is not kept, §5.4). Orders claims, does not measure elapsed time — compare it, never subtract it; use `duration` to record how long something took. Claims recorded in the same microsecond share a stamp and tie under sorting | | `active_claim` | `trust` | `string` — how the claim came to hold its value: `asserted` (someone recorded it) or `derived` (a rule concluded it). Engine-established, never caller-asserted: citing `"derived"` as a `source` still reads `asserted` | | `active_claim` | `preferred` | `bool` (a recorded judgment says act on this one) | | `active_claim` | `maintained` | `bool` — `False` when the claim holds a derived value its rule can no longer keep current: the value is the last one that rule produced, and the data behind it has moved on. `True` for every asserted claim and every derivation still being kept current. Not a conflict and not a preference — nothing disagrees, the value is simply not being kept up, and every read still serves it. Established at read rather than recorded on the claim, so it appears as soon as the rule stops keeping the value current and clears on its own; read it alongside the value it qualifies rather than carrying it forward | | `active_claim` | `written_by` | `string` (the store key that wrote the claim; empty when **no key wrote it** — either the write was not authenticated, or a rule concluded the claim and there was no write. `trust` tells those apart; `why` accounts for a derived claim as a key accounts for a written one). Observed, never asserted — contrast `source`, which the writer states | | `active_claim` | `derived_by` | `string` — what concluded a derived claim: a rule by the name `list(target = rules)` lists it under (`heat.comfy`, or `warm` for a rule compiled on its own), a computed field by its class and field qualified the same way (`heat.Box.area`). Empty on a claim someone recorded. `premises` returns what it was concluded from (§5.3) | | `active_claim` | `rule_kind` | `string` — the kind of relation the concluding rule states, when its author said (`"causal"` for `@rule(kind="causal")`, §8.8). Empty on a claim someone recorded, on a computed field's conclusion, and on a conclusion of a rule written without one | | `active_claim` | `valid_from` | `value` (union; §4.1) — the start of the claim's **validity window**: the world-time it says something about, as distinct from when it was recorded. The `null` class when the claim has no start bound, so read it by matching, never by comparing against a sentinel instant. A conclusion carries the overlap of its premises' windows | | `active_claim` | `valid_to` | `value` (union; §4.1) — the end of that window, `null` when the claim has no stated end. Only the `datetime` and `null` classes ever arise for either field, but a `match` still needs a `case default:` arm: exhaustiveness is checked against the whole union | | `active_claim` | `qualifiers` | `dict` — the facts the **writer** stated about this claim when it wrote it (`qualifiers=` on `remember` / `update`), each value keeping its class, names in name order; `{}` when it stated none. Stated, never established: the store keeps them with the claim and acts on none — contrast `trust`, which the store establishes | | `proof_tree` | `root` | `active_claim` | | `proof_tree` | `truncated` | `bool` (`True` when the proof stops short of the whole derivation) | | `verify_result` | `match` | `bool` | | `verify_result` | `actual` | `string` (the value that stands; empty at a location that is unwritten **or** in disagreement — `conflicted` tells you which) | | `verify_result` | `expected` | `string` | | `verify_result` | `conflicted` | `bool` (the location holds competing values, so there is no single `actual`; `match` still answers over all of them. A source-scoped `verify(K.s.a, v, src)` is never marked) | | `complex` | `real` | `real` — the real part | | `complex` | `imag` | `real` — the imaginary part | | `int` | `real` | `int` — the number itself | | `int` | `imag` | `int` — `0` | | `int` | `numerator` | `int` — the number itself | | `int` | `denominator` | `int` — `1` | | `bool` | `real` | `int` — `1` for `True`, `0` for `False`, the int it is | | `bool` | `imag` | `int` — `0` | | `bool` | `numerator` | `int` — `1` for `True`, `0` for `False`, the int it is | | `bool` | `denominator` | `int` — `1` | | `real` | `real` | `real` — the number itself | | `real` | `imag` | `real` — `0.0` | | `slice` | `start` | `value` — the first bound as written: an `int`, or the `null` class when it is open | | `slice` | `stop` | `value` — the second bound, the same way | | `slice` | `step` | `value` — the step, the same way | | `refuse_info` | `reason` | `string` — what was refused, written for a reader and in the script's own language. Report this | | `refuse_info` | `teaching_hint` | `string` — how to correct the call; empty when there is no suggestion. An aid to a human reader, never a second reason and never something to branch on | | `refuse_info` | `discriminator` | `string` — a short tag naming which refusal this is, for a program that must branch rather than report. The tag set is not part of this contract (§4.2), with two exceptions: a refusal from your own `raise (…)` carries the exception name you wrote, and a refusal the store raises carries its published `refuse_reason` code | | `engine_error_info` | `reason` | `string` — what failed, written for a reader | | `engine_error_info` | `code` | `string` — a short tag naming which failure this is, in the role `discriminator` plays for a refusal. The tag set is not part of this contract (§4.2) | The last two types are the handler bindings of §4.2 — a program cannot write either name as a type, and they are listed here under the names `.kind` reports. ### 11.3 Indexing element types | Value | `[int]` yields | |------------|----------------| | `claim_list` | `active_claim` | | `proof_tree` | `active_claim` | | `diagnosis` | `string` — one report line | | `string_list` | `string` | | `subject_set` | `string` | | `result_type_set` | `string` | | `string` | `string` (the one character at that position) | | `list` | `T` (negative from-end index permitted) | | `tuple` | the indexed position's type (**constant** index only) | | `dict` | `V` (indexed by a key of type `K`, not an `int`) | A `set` does not index: `s[i]` and `s[a:b]` are refused before the run, as Python refuses them (`'set' object is not subscriptable`). Walk it with `for`, in sorted order, or test an element with `in`. A `string` also supports **slicing** — `s[a:b]` (either bound optional) yields the substring, `s[a:b:step]` adds a step (a negative step reverses: `s[::-1]`), and negative indices/bounds count from the end (`s[-1]`, `s[-3:]`). Total and character-indexed; a step of `0` is a catchable refuse. ### 11.4 String methods | Method | Result | |--------|--------| | `s.find(sub)` | `int` — first character position of `sub`, `-1` if absent | | `s.count(sub)` | `int` — number of non-overlapping occurrences (empty `sub` = `length + 1`) | | `s.startswith(prefix)` / `s.endswith(suffix)` | `bool` — whether `s` begins / ends with the argument (a string, or a tuple of strings → any of them) | | `s.replace(old, new)` | `string` — copy with all occurrences of `old` replaced (empty `old` inserts `new` in every gap) | | `s.upper()` / `s.lower()` | `string` — full Unicode case mapping (may expand, e.g. `ß`→`SS`, so length can change; `lower` applies the Greek word-final `Σ`→`ς` rule) | | `s.strip([chars])` / `s.lstrip([chars])` / `s.rstrip([chars])` | `string` — remove characters from both ends / left / right (whitespace, or the `chars` set) | | `s.partition(sep)` / `s.rpartition(sep)` | `tuple` — split once at the first / last `sep` into `(before, sep, after)` | | `s.translate(table)` | `string` — each character whose code point `table` holds replaced by what it holds for it | | `s.splitlines([keepends])` | `list` — the lines of `s`, split at each of CPython's eleven line boundaries, U+2028 and the separator `\x1e` among them; a true `keepends` keeps each line's boundary | All are total (never refuse) and count characters, except `partition` / `rpartition`, which refuse on an explicit empty separator, and `translate`, which refuses a code point no character has. **What a type itself answers.** Three methods are a type's own, called on its name as in Python. A name the program binds is no longer that type. | Call | Returns | |---|---| | `str.maketrans(x, y)` | `dict` — each character of `x` to the character at its place in `y`, as code points. Strings of different lengths refuse with `value_error` | | `str.maketrans(d)` | `dict` — the dict `d`, each string key as its code point. A string key not one character long refuses with `value_error`, and a key that is no `string`, `int` or `bool` with `type_error` | | `dict.fromkeys(xs, v)` | `dict` — each element of `xs` to the one value `v` | | `float.fromhex(s)` | `real` — what `s` spells in hexadecimal. A string that spells nothing refuses with `value_error`, a value too large with `arithmetic_overflow`, and `inf` or `nan` with `conversion_error` | Python answers `str.maketrans(x, y, z)` and `dict.fromkeys(xs)` with `None` among the values, which has no spelling here, so each is told what to write instead. A value of the type reaches each of them too, as in Python: `"a".maketrans("b", "c")`. **A method through its type's name.** `T.m(x, …)` is `x.m(…)`, its first argument the receiver, as in Python: `str.upper("a")` is `"a".upper()`, and `list.append(xs, 2)` is `xs.append(2)`. `T` is `str`, `int`, `bool`, `float`, `complex`, `list`, `dict`, `set` or `tuple`. No receiver, a receiver of another type and a name the type does not hold are each a `type_error` in Python's words. A name the program binds is no longer that type. --- ## 12. Keywords The keyword surface **localises per natural-language surface**, and the surface is chosen per call ([MCP Reference §8.1](/ref/dke/mcp/#what-language-selects), which names the tag for each). This Reference describes the English one, where the structural keywords are English: `def` `class` `return` `match` `case` `if` `else` `for` `in` `try` `except` `as` `branch` `commit` `rollback` `and` `or` `not` `print` `True` `False` `None` `null`. Python's own keywords and `null` are reserved, as they are in Python, and name nothing, including those this language has no statement for, such as `async`, `await`, `global`, `lambda`, `nonlocal`, `while` and `yield`. `None` is spelled so on every surface, being Python's word for a value this language does not have rather than one it translates. Verb names (`remember`, `current`, `get`, …) localise correspondingly (§5), and on this surface they are the English spellings this document uses throughout. A word of this language's own that Python keeps no keyword of is an ordinary name where its form does not stand: a verb's word is the verb only right before its `(`, and `match`, `case`, `branch`, `commit` and `rollback` name what they do only where those forms stand, so `get = 1` and `def f(match: int):` read as they do in Python. Nine words have an English spelling and no other. Two are the diagnostic verbs. A unit written on another surface cannot call them, and writing the English spelling does not reach them either — on that surface the word is not a verb at all. The compiler says so and names the remedy, which is the unit's `language` tag rather than an edit to the program: *Compile this unit as `eng` to use it.* The other two are the words a `trust(...)` premise compares with (§8.8), `"asserted"` and `"derived"`. They are the values `.trust` reads back, which are English on every surface, so a premise written on any surface compares with them as they are spelled here. The next two are `real` and `imag`, the names `complex(...)` takes its two parts by (§6.3). They are also the `.real` and `.imag` attributes (§11.2), which read the same on every surface. The last three are `maketrans`, `fromkeys` and `fromhex`, the methods a type itself answers (§11), spelled as Python spells them, as a string's methods are. Everything else in the keyword surface localises: the `except` handler kinds, `print`'s keyword arguments, the method receiver, the compound verb heads, and every other keyword argument a built-in function takes. **English on every surface.** `why_not` `what_needs` `asserted` `derived` `real` `imag` `maketrans` `fromkeys` `fromhex` `world` (§7.18) is a **contextual keyword** — a block only right after `with`, an ordinary identifier elsewhere. Type names (`string`, `int`, …) are likewise contextual, not reserved — but avoid all of these as identifiers. **A name is read as Python reads it**, in Unicode's Normalization Form KC, so every spelling of one form is one name: `x` (U+FF58) is the name `x`, and `e` followed by U+0301 is `é`. A keyword is compared as it is written, so `if = 1` binds the name `if`. A string is not read: a variable holding `"x"` addresses `x`, where the path `K.x.v` addresses `K.x.v`. A name that keeps a combining mark from U+0300 to U+036F with no letter to compose into may name a local, a parameter or an alias, and nothing a store holds. --- ## 13. Quick reference card ``` DECLARE def name(K: string, n: int): # `def`; name: type params ASSIGN hist = get(K.s.a) # bare assignment PRINT print("x = " + str(n)) # string|int|bool|discriminator WRITE remember(K.s.a, v, src) update(K.s.a, v, src) forget(K.s.a) assert(K.s.a, src) pin(K.s.a) forget(K.s) unpin(K.s.a) READ current(K.s.a) -> active_claim get(K.s.a) -> claim_list why(K.s.a) -> proof_tree verify(K.s.a, v[, src]) -> verify_result why_not(K.s.a) -> diagnosis what_needs(K.s.a) -> diagnosis subjects(K.a, v) -> subject_set list_attributes(K) -> string_list caveats(K.s) conflicts() list_values(K.a) list_subjects(K[.a]) LISTS list_categories() list_pinned() list_scripts() list_result_types() SCRIPT list_scripts() -> string_list TYPES string int bool | active_claim proof_tree verify_result empty (discriminators) claim_list proof_tree diagnosis string_list subject_set result_type_set (collections) OPS == != < > <= >= (int cmp) and or not (bool) + - * / % (int) + (string concat) .field [i] | & - ^ <= < (set) COLLS [1, 2, 3] list (1, "a") tuple {"k": 1} dict {1, 2} set # constructed, never stored xs[i] xs[a:b] x in xs .length [e for x in xs if c] zip(a, b) enumerate(xs) range(a, b) iter(xs) next(e) next(e, default) # one-pass iterator sum(xs) min(xs) max(xs) min(a, b, c) # reduce (vs sum(K.attr) store fold) sorted(xs) sorted(xs, reverse=True) any(xs) all(xs) bool(x) tuple(xs) d[k] d.get(k, x) k in d d.keys() d.values() d.items() s.union(t) s.intersection(t) s.issubset(t) s.issuperset(t) s.isdisjoint(t) set([]) c.copy() dict(d) CHANGE xs.append(v) xs.extend(ys) xs.insert(i, v) xs.remove(v) xs.sort() xs.reverse() s.add(x) s.discard(x) s.update(t) d.update(e) c.clear() xs[i] = v d[k] = v d[k] += v del xs[i] del d[k] # each changes it in place (§7.17) IF if c: … / else: … FOR for c in list: … # finite collections only (list / dict-keys / set) MATCH match d: case active_claim: … / case empty: … match n: case 1 | 2: … / case k if k > 2: … / case _: … # value patterns TRY try: … / except refuse as r: … / except engine_error as e: … BRANCH with branch("b"): … commit / rollback WORLD create_world(n) with world(n): … compare_worlds(a[, b]) -> claim_list merge_world(n) discard_world(n) list_worlds() -> string_list CALL name(args) # plain call CLASS class Sensor: temp: string def record(self, v: string, src: string): remember(self.temp, v, src) def label(self) -> string: return "sensor" INSTANCE s = Sensor(room) remember(s.temp, "21", "op1") s.record("22", "op1") reading = s.temp name = s.label() match s: case Sensor: … / case default: … ERRORS parse type (static) refuse / engine_error (runtime, catchable) HELP --help [header|types|verbs|statements|scripts] ``` --- ## 14. Python tooling and type checking DKE Python is a Python-syntax surface: a standard Python parser accepts `.dpy`, so **formatters** (black), **linters** (ruff / flake8), **language servers**, and syntax highlighting work on `.dpy` files as they stand. For **type checkers** (pyright / mypy), which additionally want every name in scope, DKE ships a stub, **`dke.pyi`**, declaring the builtin surface — the verbs, the value factories, the type names, the `@rule` decorator, and the `branch` context manager, none of which the source imports. A checked copy that opens with the one-line prelude `from dke import *` then type-checks; the service compiles the original source unchanged. The core surface (verbs, slot paths, values, control flow, rules, aggregates, transactions) checks clean. Two DKE constructs mean something different to a Python checker than to the DKE service — class construction (`Sensor(room)`) and the `case as :` type match — so relax the codes they trigger (the `dke.pyi` header lists the exact settings). The DKE service remains the authority on types. --- ## 15. The knowledge model DKE keeps apart several things a store usually runs together. This chapter names each distinction and the verbs that make it, and points to the section where each verb is described. It adds nothing those sections do not say. | the distinction | what you write or read | where | |---|---|---| | **unknown is not false** | A cell nobody wrote reads `empty` from `current`. A cell recorded as known to be absent, with `assert(K.s.a, src)`, reads back as a claim whose value is `null`. A rule records one with `assert(p.chip)` as its conclusion. `False` is an ordinary value. A rule tells the three apart: `absent(...)` holds for the cell nobody wrote, `known_absent(...)` for the recorded absence, and a comparison for the value. `not_asserted(...)` holds where no person wrote, whatever a rule concluded, and `why_not` says which empty case a read met. | §5.1 `assert`, §5.2 `current`, §5.3 `why_not`, §8.8 | | **disagreement is not uncertainty** | Two sources that record different values at one cell are both held, and neither replaces the other. `conflicts()` and `caveats(K.s)` find such cells, `agreement` counts the sources behind a value and `writers` the keys that wrote it. None of them grades a source: which to believe is your judgment. | §5.2, §5.3 | | **a preference is not a truth** | `prefer(K.s.a, src)` records which source you go by. Every claim stays, the cell stays in disagreement, and no rule concludes differently; the claim you go by reads `.preferred`. | §5.1 `prefer`, §11.2 | | **revision is not contradiction** | A source revising its own reading writes `update`, and `get` still returns what it replaced. Another source saying something else is a disagreement, above. | §5.1 `update`, §5.2 `get` | | **an alternative is not the store** | `with world("w"):` writes into a named world that the store does not see, kept across runs until `merge_world` adopts it or `discard_world` drops it. `compare_worlds(a, b)` returns the claims one holds that the other does not, conclusions included. | §5.9, §7.18 | | **a hypothesis is not a fact** | `suppose(...)` reads what a field would be if other fields held the values you give, with standing rules running under them, and keeps nothing. `suppose_absent(...)` asks the same with facts taken away. Writes inside a `branch` can still be rolled back. | §8.9, §7.10 | | **concluded is not recorded** | `.trust` reads `asserted` on a claim someone recorded and `derived` on one a rule concluded, and nobody can write it. `.derived_by` names the rule, `premises(c)` returns the claims it concluded from, `why` returns every claim that took part in the derivation, and `.rule_kind` the kind of relation its author said the rule states. | §5.3, §8.8, §11.2 | | **how it is known travels with the claim** | A writer can state how it knows, as `qualifiers={"method": "observed", "confidence": 0.8}`, and every read returns them as `.qualifiers`. They are the writer's statement: the store keeps them and acts on none. | §5.1 `remember`, §11.2 | | **when it holds is not when it was known** | A validity window says when a fact holds, and an as-of `datetime` reads along it. `known_at=` reads the other axis: what the store held at a given moment. | §5.1 `remember`, §5.4 | | **what rests on what** | `dependents(c)` returns the claims concluded from `c`, and `dependents(c, transitive=True)` everything resting on it at any distance. `would_forget(...)` counts what a range removal would take before you make it. | §5.3 | | **a claim keeps its name** | `.claim_id` names a claim on every read that returns it, and other writes leave it unchanged, so a program can keep the id and hand it to `dependents` or `premises` later. A claim recorded anew, such as a conclusion drawn again, has an id of its own. | §5.3, §11.2 | --- *Companion documents: the [**Python Tutorial**](/tutorial/dke/python/), which teaches this language in task order, and the [**MCP Reference**](/ref/dke/mcp/), which specifies the transport that carries a program to the service.* ## Revision history - **13.0.0** · **rev 148** (2026-11-12) — The Reference states a version of its own again, first on the Version line and followed by the revision it last changed in; the number continues the one it last stated, 12.0, rather than starting over. The opening of this history says so, and from this revision each entry opens with the version it made (Python Specification §11.1). Nothing else changed. - **rev 147** (2026-11-12) — The API version is `MAJOR.MINOR`, and the Version line states API 1.0. A first-line declaration's `python.1` is the API's MAJOR, valid at 1.0 and every later 1.N. - **rev 146** (2026-11-12) — Aggregate queries: a fold whose Python computation passes the largest `real` on the way answers its value where that value is a `real` (over `1e308` and `1.5e308`, `avg` yields `1.25e+308`), and `inf` or `-inf` where the value itself is past it. - **rev 142** (2026-11-12) — A `real`, and a part of a `complex`, may be `inf`, `-inf` or `nan` (§4): `1e400` is `inf`, `math.inf`, `math.nan`, `math.isinf`, `math.isnan`, `math.isfinite`, `cmath.inf`, `cmath.nan`, `cmath.infj`, `cmath.nanj`, `cmath.isinf`, `cmath.isnan` and `cmath.isfinite` are added, and `float()` and `complex()` read the three words (§6.14). A result past the largest `real` is `inf` or `-inf` where it refused, an operation with an infinite or NaN operand answers IEEE 754's value, and each departure from Python is stated: §6.3 *Infinity and NaN*, §6.1 *Infinity and NaN* (one NaN, found by value, sorted after `inf`, `nan` from `min` and `max`), §6.14 (the `math` and `cmath` functions) and the `struct` and `vector` sections. A cell holds every real exactly, `-0.0` included. - **rev 141** (2026-11-11) — §6.14: `math` gains thirty names, with Python 3.11's signatures and words: `gcd`, `lcm`, `comb`, `perm`, `factorial`, `isqrt`, `prod`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `cbrt`, `exp2`, `expm1`, `log1p`, `pow`, `fabs`, `copysign`, `fmod`, `remainder`, `trunc`, `modf`, `frexp`, `ldexp`, `fsum`, `dist`, `nextafter` and `ulp`. Each answers the exact value rounded once; `fsum`, `prod` and `dist` take every operand exactly, so `math.fsum([1e308, 1e308, -1e308])` is `1e+308` where Python raises, and `math.hypot` of three or more coordinates is the same exact distance as `math.dist`. An `int` answer past what an `int` holds is refused as any such `int` is. - **rev 140** (2026-11-11) — §7.4: two `**` displays written out that give one name, with a `**` the program reads merged before the second giving, are refused as the program runs, in the order Python merges, and are catchable: with `n` holding `"x"`, `print(**n, **{"x": 1}, **{"z": 1}, **{"z": 2})` is `print() got multiple values for keyword argument 'x'`, where it named `'z'`, refused where the unit was compiled. Where every mapping is written out, or no `**` is read before the second giving, the unit is compiled as before. - **rev 139** (2026-11-11) — §7.4: a `**` display written beside a `**` whose keys the program reads, whose key is no string, is merged with the mappings as the program runs, as Python merges a call's keywords: with `n` and `k` both holding `"x"`, `print(**{1: 2}, **n, **k)` and `len(xs, **n, **{1: 2}, **k)` are `print() got multiple values for keyword argument 'x'` wherever the display stands, and `keywords must be strings` answers only where no name is given twice. It was `keywords must be strings` where the unit is compiled. - **rev 138** (2026-11-11) — §7.4: a list's `sort` given a `*` and a `key=` the elements refuse, `xs.sort(*ys, key=len)` over a list of `int`s, counts its arguments before it applies the key, as Python does: with `ys` giving one value it is `sort() takes no positional arguments`, where it was `object of type 'int' has no len()`, refused where the unit was compiled; with `ys` giving none it is that refusal of the key, as the program runs and only where the sort has an element to apply it to. Each is catchable. A `*` nothing types, and a sort with no `*`, keep the key's refusal where the unit is compiled, in the same words. - **rev 137** (2026-11-11) — §7.4: a list's `sort` given a `*` and a positional argument written beside it, `xs.sort(*ys, 5, x=1)`, counts every argument before it refuses the positional one, as Python does: with `ys` giving one value it is `sort() takes at most 2 arguments (3 given)`, where it was `sort() takes no positional arguments`, refused where the unit was compiled; it is decided as the program runs and is catchable. A `*` nothing types keeps its refusal where the unit is compiled, in the same words. - **rev 136** (2026-11-11) — §7.4: a list's `sort` whose receiver is written, `xs.sort(*ys, x=1)` or `list.sort(xs, *ys, x=1)`, counts what a typed `*` gives before it names a keyword it does not take, as Python does: with `ys` giving one value it is `sort() takes no positional arguments`, where it was `'x' is an invalid keyword argument for sort(); it takes `key`, `reverse``, refused where the unit was compiled; with `ys` giving none it is `'x' is an invalid keyword argument for sort()`, as the program runs, catchable and without the hint. A `*` nothing types keeps its refusal where the unit is compiled, in the same words. - **rev 135** (2026-11-11) — §7.4: a key that is no string which two `**` mappings of a call give is a name given twice, refused before any key is refused as no string, as Python merges a call's keywords: with `m` and `n` both holding `1`, `print(**m, **n)`, `len(xs, **m, **n)`, `sorted(xs, **m, **n)`, `s.split(**m, **n)` and a script's `f(**m, **n)` are `print() got multiple values for keyword argument '1'`, where they were `keywords must be strings`. Keys are equal as a dict's are (`1`, `1.0` and `True` are one) and the later is named as `str` writes it. Written as displays, `print(**{1: 2}, **{1: 3})` is refused where the unit is compiled, in the same words. - **rev 134** (2026-11-10) — §3: a source may say what it is written in on its first line, `# dke: edition=python.1 language=eng.1`, with either version left out to mean the one the service serves. A declared name supplies a parameter the call leaves out, and a parameter naming another value is refused, as is a version the service does not serve. - **rev 130** (2026-11-10) — §8.12: a constraint premise that orders a string field against a quoted bound is judged, alone or with `absent()`, and is reported violated exactly when a rule with the same premise would conclude. §5.3: the `proof_tree` a `why` yields is the derivation alone, and a cell's earlier, superseded values are no longer elements of it; `get` reads them. §7.9: `except engine_error` takes every run-time failure the wire reports as `engine_error`, including a value narrowed to a class it did not hold and a read of a method that returns nothing; a spent budget, a stopped run and an add-only refusal remain uncatchable. §6.3: a collection takes no format spec, and an empty one answers `str` of the value. - **rev 128** (2026-11-09) — `cmath.exp`, `cmath.rect` and `cmath.sqrt` give each part, real and imaginary, as its exact value rounded once, so `cmath.exp(complex(1.3, 2.7)).imag` is `1.5681835703495033` where Python answers `1.5681835703495035`. - **rev 127** (2026-11-09) — `sum` over numbers holding a `real` is the exact sum rounded once, so `sum([0.1] * 10)` is `1.0` where Python 3.11 answers `0.9999999999999999`, and an `int` enters as the integer it is; it is refused only when its rounded value, or an all-`int` total, is out of range. The store fold's `sum` and the list's agree. - **rev 122** (2026-11-08) — §6.14: `math` has `log10`, `log2`, `erf`, `erfc`, `gamma` and `lgamma`; `math.log(x, base)` is rounded once, so `math.log(1000, 10)` is `3.0` where Python answers `2.9999999999999996`. - **rev 120** (2026-11-08) — §6.14: `math` has `asin`, `acos`, `atan`, `hypot`, `degrees` and `radians`; `degrees`, `radians` and `hypot` of two coordinates are correctly rounded where Python's algorithm rounds twice, with an example where the last digit differs from Python's. - **rev 112** (2026-11-06) — §6.4, §6.5: a blob indexes, slices and joins as Python's `bytes` do, with a program showing a cut, a reversal, a byte and a join. - **rev 111** (2026-11-06) — §6.15: `vector.sumsq(xs, axes)` adds along axes, as `vector.apply` carries a matrix along them: one `math.fsum` of the squared parts of the elements for each pattern of the named axes, a list of 2ᵏ reals, in one call. - **rev 110** (2026-11-05) — §7.4: `dict.fromkeys`, `str.maketrans` and `float.fromhex`, the three methods a type answers itself, read a `**` as every callee that takes no name does: with `opts` holding `x`, `dict.fromkeys(xs, 0, **opts)` is `dict.fromkeys() takes no keyword arguments`, with a key that is no string `keywords must be strings`, and with `opts` empty the call as written. It was a parse_error that `**` spreads a collection the call does not read. - **rev 109** (2026-11-05) — §7.4: a change that would give its receiver a second type, beside a `**` whose keys are known only as the program runs, waits for the run: `s.add("a", **opts)` after `s = {1}` is `set.add() takes no keyword arguments` with `opts` holding `x`, and with `opts` empty the refusal a name's one type gives, as the program runs. It was that refusal at compile whatever `opts` held. - **rev 108** (2026-11-05) — §7.4: `slice` called beside a `**` refuses any name before it reads a key, as `math.log` does: `slice(1, **m)` with `m` holding a key that is no string is `slice() takes no keyword arguments`. It was `keywords must be strings`. - **rev 107** (2026-11-05) — §7.4: beside a `**` whose keys are known only as the program runs, every mapping is merged before a key that is no string is refused, so a name given twice is refused first (`print() got multiple values for keyword argument 'x'`), and a name written beside such a `**` that `sorted`, `min`, `max` or a list's `sort` does not take waits for the merge: `sorted(xs, x=1, **opts)` is `sorted() got multiple values for keyword argument 'x'` where `opts` holds `x`. It was `keywords must be strings` and a `type_error` where the unit is compiled. - **rev 106** (2026-11-05) — §7.4: a method reached through its type's name given only such a `**`, with no positional argument, merges the names and is then refused for its missing receiver, as the program runs: `str.upper(**opts)` is `unbound method str.upper() needs an argument`. It was a parse_error. - **rev 105** (2026-11-05) — §7.4: a list's `sort` through its type takes its list from the first thing every `*` and every positional argument gives, in order, and counts and names what follows it only after, as Python does: `list.sort(*xs, ys)` sorts `ys` where `xs` gives nothing. A list written of another type is refused for its type before what follows it is counted or named: over a tuple `t`, `list.sort(t, 1)` is `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object`, where it was `sort() takes no positional arguments`. - **rev 104** (2026-11-05) — §7.4: beside a `**` whose keys are known only as the program runs, an argument a callee that takes no name does not take is refused as the program runs, after the names, in the words the call has without such a `**`: with `opts` holding `x`, `s.center(1.5, **opts)` is `str.center() takes no keyword arguments`, and with `opts` empty `'float' object cannot be interpreted as an integer`. So is a change's, and a dict's `get` or `setdefault` of a key alone. Each was a `type_error` where the unit is compiled, and still is without such a `**`. A value bound from such a call is refused as the program runs where the call fixes a type, and stays a `type_error` where the unit is compiled where nothing types what it answers, `v = abs("a", **opts)`. - **rev 103** (2026-11-05) — §6.17, §7.4: a special name beside a `*` or a `**` before any dict is read as at any method, its names refused in Python's words before its count, as the program runs; beside a `*` it was refused in words that were not Python's, and beside a `**` whose keys are known only as the program runs it was a parse_error. Beside such a `**`, a change read as a value and bound to no name is made and then refused as the program runs, after its names, and so is a special name whose answer no value here holds; each was a `type_error` where the unit is compiled, and stays one where the value is bound. - **rev 102** (2026-11-04) — §7.4: beside a `**` whose keys are known only as the program runs, a value bound from a call the run can only refuse, where nothing types what the call would answer, is a `type_error` where the unit is compiled, in the call's own words, whatever the mapping gives: `v = list.pop((1, 2), **opts)` is `descriptor 'pop' for 'list' objects doesn't apply to a 'tuple' object`. The same call read as a statement or printed is refused as the program runs. This states what rev 101 already did. - **rev 101** (2026-11-04) — §7.4: beside a `**` whose keys are known only as the program runs, a count that a builtin, a function of a built-in namespace or a built-in value's method taking no name does not take is refused only where the mapping gives no name, as the program runs: with `opts` empty, `len(xs, 2, **opts)` is `len() takes exactly one argument (2 given)`. It was a `type_error` where the unit was compiled, and stays one without such a `**`. - **rev 100** (2026-11-04) — §7.4: beside a `**` whose keys are known only as the program runs, a name written at a change is merged with the names it gives, and through a type's name a receiver written of another type is refused once they are merged, as the program runs. Each was a `type_error` where the unit is compiled, whatever the mapping held, and still is without such a `**`. - **rev 99** (2026-11-04) — §7.4: through a type's name, where the receiver is the first thing a `*` gives, every method merges the names a `**` gives before it takes the receiver, as `list.sort` did, and refuses a receiver of another type after them, as the program runs; so it does where nothing types what the `*` gives. It took the receiver first. - **rev 98** (2026-11-04) — §7.4: a `**` before any dict beside a `*` whose length is known only as the program runs is read at `list.sort` where the list is the first thing the `*` gives, the names merged before the list is taken. It was a parse_error. - **rev 97** (2026-11-04) — §7.4: a `**` before any dict beside a `*` whose length is known only as the program runs is read at a list's `sort` and a dict's `update`. It was refused, and stays a parse_error at `list.sort` where the list is the first thing the `*` gives. - **rev 96** (2026-11-04) — §6.13: `math.isclose` places an `a` or `b` given by name beside a `*` at the count the `*` gives, where it took them by name only in a call with no `*`. §7.4: a `**` before any dict beside a `*` whose length is known only as the program runs is read at a built-in value's method that takes names and at `math.isclose`. It was a parse_error, and stays one at a list's `sort`. - **rev 95** (2026-11-04) — §7.4: a `**` before any dict beside a `*` whose length is known only as the program runs is read at `sorted`, `min` and `max`, which count the `*` before they read a name. It was a parse_error, and stays one at `math.isclose` and a method that takes names. - **rev 94** (2026-11-04) — §7.4: a `**` before any dict beside a `*` whose length is known only as the program runs is read at a builtin that takes names, `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum` and `dict`, over the count the `*` gives. It was a parse_error, and stays one at `sorted`, `min`, `max`, `math.isclose` and a method that takes names. - **rev 93** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, beside a `*` whose length is known only as the program runs, is read where what is called takes no name, before the count the `*` gives. It was a parse_error, and stays one where what is called takes names. - **rev 92** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at a function of `math`, `cmath`, `vector` or `struct` gives it its names as Python's does: `math.isclose` takes its names, and any other function refuses any name. It was a parse_error. - **rev 91** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at a method that changes a collection gives it its names as Python's does: a list's `sort` takes `key` and `reverse`, a dict's `update` takes every name as a key, and any other change refuses any name before the collection changes. It was a parse_error. A call that no names its mapping could give would let run is refused as the program runs, in the words of what the mapping gives; it was a type_error where the unit is compiled. - **rev 90** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at a built-in value's method called as a value gives the method its names as if written: `split`, `rsplit`, `splitlines` and `expandtabs` take theirs as Python's do, and any other method refuses any name, `str.upper() takes no keyword arguments`. It was a parse_error. - **rev 89** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at `sorted`, `min` and `max` gives the call its names as if written, counted apart from the positional arguments, and `sorted` is refused under `sort`'s name, as Python's is. A `key` the mapping gives is refused `'int' object is not callable` once there is an element to call it on. It was a parse_error. - **rev 88** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at `enumerate` and `sum` gives the call its names as if written, and `enumerate` reads them where they stand, as Python's does, so a `start` given alone is `'start' is an invalid keyword argument for enumerate()`. It was a parse_error. A call that no names its mapping could give would let run is refused where the unit is compiled. - **rev 87** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at `int`, `round`, `pow`, `complex`, `str` and `zip` gives the call its names as if written, and at `dict` its keys; a name the callee does not take, too many in all, and any refusal the call written out meets are catchable `type_error`s in Python's words and order, and a call whose type would turn on the names given is refused where the unit is compiled. It was a parse_error. - **rev 86** (2026-11-04) — §7.4: a `**` before any dict, such as a name that holds one, at a builtin that takes no name (`len`, `abs`, `list`, `isinstance` and the rest) is read after the positional arguments; a mapping that gives nothing leaves the call as written, and any name it gives is a catchable `type_error` in Python's words. It was a parse_error. - **rev 85** (2026-11-04) — §7.3, §7.4, §7.13: §7.3 is titled *What a value prints as* and states how every value is written, a read's included: a counted preview such as `2 entries [felix, tom]`, `proof_tree(2)`, a diagnosis's own text, an active claim's value, `verify_result match=True` and ``. It said such a value could not be printed directly. §7.13 points to it for each argument's text. §7.13: a `**` before any dict, such as a name that holds one, gives `print` its keys as keywords when the line is composed, merged and checked as Python does, each fault a catchable `type_error`; it was a parse_error, and §7.4 says `print` is the exception. - **rev 83** (2026-11-03) — §6.14: a wrong number of arguments to a `math` or `cmath` function is a type error in Python's words, `math.sqrt() takes exactly one argument (2 given)`, and no longer a parse_error. - **rev 82** (2026-11-03) — §6.3, §7.13: a keyword `print`, `sorted` or a list's `sort` does not take is a type error in Python's words, `'x' is an invalid keyword argument for print()`, named for `sort()` from `sorted` as Python names it. - **rev 81** (2026-11-03) — §6.13: a name beside a `*` of a length known only as the program runs binds as written out at a built-in method, a `dict`'s `update` and a list's `sort`: `"a b c".split(*ys, maxsplit=1)`, `d.update(*ys, k=1)`, `xs.sort(*ys, reverse=True)`. - **rev 79** (2026-11-03) — §7.4: a `**` before a dict display whose keys are all literals at `print`, a builtin, a built-in type's method, `sorted`, a list's `sort` and a `math` function. §7.13: `print`'s `file` may be given `None`. - **rev 78** (2026-11-03) — §8.10: a read through a family of classes runs as Python's does until it reaches a class whose method returns nothing, in a method a subclass inherits and in a script written above the class too. A call whose arguments the method of the instance's class does not take is refused as it reaches that class, in the words of Python's `TypeError`. A read no class can answer, and a call no class takes, stay refused when the program compiles. - **rev 77** (2026-11-03) — §6.3: `pow` with a modulus given an operand that is not an integer is refused in Python's words. §6.4, §7.17: `xs += c` on a list takes any collection, as `extend` does. §6.14: `math.floor` and `math.ceil`. - **rev 76** (2026-11-03) — §6.17: a special name a built-in value holds, called by name, answers as the spelling it stands for, and where Python's answer is one no value here holds, the call is a type_error saying so. - **rev 75** (2026-11-03) — §5.8: a fold over `real` values is exactly rounded where it adds (`sum` is `math.fsum`, `avg` `statistics.fmean`, `rms` and `geomean` likewise), and `covariance` is the population figure. §6.3: `sum` over a list adds left to right, as CPython 3.11's does. §6.12: an iterator is not a key or a set element, and why. - **rev 74** (2026-11-03) — §6.6, §7.8: an argument of a type a string's method does not take, a list's or a tuple's `index` bound and a list's `insert` position are refused in CPython's sentence, where the argument is of a class CPython has, and `join` names the first position that holds no string; a shape arm's alternative that can never match is left out, the arm standing on the others. - **rev 73** (2026-11-03) — §6, §6.9, §6.10: an operator given operands it does not take is refused in CPython's sentence, naming CPython's classes, where CPython refuses the same operands; a set, or a dict's keys, that would hold a tuple beside a scalar is refused naming the two types, as a list is. - **rev 72** (2026-11-03) — §6.7, §6.12, §7.17: a set's `update` and `difference_update`, and a dict's `update`, read an iterator or a generator expression one element at a time, as in Python, so one over the collection being changed is refused at its next read and what was written before stays; a list's `extend` over the list itself stops at the length it began with. - **rev 71** (2026-11-02) — §6.9, §6.10: a union across scalar element types is a `set`, as in Python, and a keys or items view takes any collection beside `&`, `|`, `-` and `^`. - **rev 70** (2026-11-02) — §8.10: a value read through a call that reaches a class whose method returns nothing runs that method and is then refused as the line runs, naming it. - **rev 69** (2026-11-02) — §8.11: a `value` built from scalars holds their classes, and where a test of a name holding one fails, the name is read at the classes left to it: in the `else`, in a negated test's branch, in a conditional's second branch, after the test in an `or`, and in a `match` arm below arms naming classes. A branch no `value` can take is never run and not read, and an operator meeting a `value` of known classes runs where it takes each. §6.2a: two scalars that are not both numbers join to a `value` holding their classes, and the second branch reads a tested name at what the test failing leaves it. §7.8: an arm below arms naming classes reads the scrutinee at what their tests failing leave it. - **rev 68** (2026-11-02) — §4.1, §8.11: a name holding a `value` is narrowed by an `isinstance` test, in the branch it takes, after it in an `and`, after a comprehension's `if`, in a conditional's first branch and in the body of a `match` arm whose guard makes it. §7.8: a `match` over a `value` whose class arms all write Python's parentheses is Python's, every arm a test and none required, and `case int(n):` and a capture bind the value; a guard narrows its arm's body. §6.2a: a scalar beside a `value` joins to the `value`. - **rev 67** (2026-11-02) — §4.1, §6.8: a tuple of scalars, or of class instances, iterates and is indexed at a computed position as a list of its elements is; only one holding a collection beside a value of another type is not. §6.3: so it converts, and `dict(x)` reads any collection of pairs as `update` does. §6.5: a tuple sliced at a computed bound, and an index of the wrong type told in Python's words. - **rev 66** (2026-11-02) — §7.8: a `match` over a value Python has is Python's: every arm a test and none needed, class patterns at an arm's head or inside another pattern, and a type that reads itself holding one. §8.6: a class match with `()` is a test. - **rev 65** (2026-11-02) — §7.1, §7.12: a script with no `-> type` whose body holds a `return ` is a value script of the type its `return`s give. §7.4: the name of a `def`, a method or a module read with no call is told no value holds it. §8.4, §8.5: so a method's. - **rev 64** (2026-11-02) — §7.8, §9.3, §10: a name nothing binds, and a script a held module does not have, are told in Python's words. - **rev 63** (2026-11-02) — §7.4: a call that does not fit is told in Python's words and order; a call of a value is a `type_error` in Python's words, and no longer a parse_error. §8.4: a method's refusal counts its receiver wherever it is told. §8.11: a class tests any value, and every value is an `object`. Newest first. The revision is the surface's — one number stated by every published document, the web pages and the service wire, moving whenever any document changes. This list records the revisions at which this document changed and what changed in it; a revision absent here left this document as it was. The Version line at the top states this document's version, the revision it last changed in and the current revision; from rev 148 each entry opens with the version that revision gave this document. The history starts at rev 1. What a revision promises is stated in the [Python Specification](/spec/dke/python/), its section 11.1. - **rev 62** (2026-11-01) — §8.1: a class exists from the line that declares it, and a body or a string in a signature may name one declared below. - **rev 61** (2026-11-01) — §7.1: a declaration may call one above or below it, and a module's own statement reaching a `def` below it is told `name 'g' is not defined`. §8.5: a value method's body holds any statement, and a method may call one written below it. §8.10: a method call through classes sharing no base runs the instance's own. - **rev 60** (2026-11-01) — §6.9: a key no dict could hold is told in Python's words, written or looked up. §6.12: what stops reading an iterator once the answer is known. - **rev 59** (2026-11-01) — §6.13: a `*` at a built-in method's call, and a count a built-in method does not take in Python's words. §6.10, §7.17: a set's `union`, `intersection`, `difference` and `update` take any number of collections, with `difference_update`, `intersection_update` and `symmetric_difference_update` beside them. §6.7: a generator expression right after a `*`. - **rev 58** (2026-11-01) — §6.4a: a format spec's sign, `z`, `#` and grouping format as Python's do. §11.4: a method through its type's name. §6.9: a dict's `|=` reads what `update` reads. §7.4: a `**` at a script's call. - **rev 57** (2026-11-01) — §6.4a, §6.6: a string's `.format`, `.format_map` and `%` format as Python's do, and `translate` takes a list, a tuple or a string as its table. §6.3: `@` and `@=` read and are refused naming their operands. §6.9, §6.10: a dict's `**`, `|` and `|=` merge. §7.15: `raise` holds several arguments. §11: a type's own three methods are reached through a value. - **rev 56** (2026-11-01) — §7.19: a named expression (`:=`) reads. §7.13: `print`'s `sep` and `end` are any expression that gives a string, and `flush` is read. §7.16: a condition of `assert` may begin with a bracket. - **rev 55** (2026-11-01) — §6.4a, §6.6, §11.4, §12: `s.translate(table)`, and what a type itself answers: `str.maketrans`, `dict.fromkeys` and `float.fromhex`. A string has no `.format_map`. Three more words are English on every surface. - **rev 53** (2026-11-01) — §4.1, §11.2: a `real` takes `is_integer` and `hex`, an `int` or a `bool` takes `bit_length` and `bit_count`, and each takes `as_integer_ratio`. An `int` and a `bool` have `.numerator` and `.denominator`. - **rev 51** (2026-11-01) — §6.7: a comprehension's `if` filters are each asked only for a truth value. - **rev 50** (2026-10-31) — A `string`, a `set`, a `dict` (its keys) and a dict's view unpack into a target list. - **rev 48** (2026-10-31) — `key=` takes a method of a built-in type, `key=str.lower`, over elements of that type. - **rev 47** (2026-10-31) — §12: a name is read as Python reads it, in Normalization Form KC, a keyword is compared as written, a string is not read, and a name that keeps a combining mark may name a local and nothing a store holds. - **rev 46** (2026-10-31) — §7.12: a `return` outside a script is refused where it ended the program. §9.3: `from __future__ import` is read as Python reads it. §10: what Python's compiler refuses is answered at its place and in its order. - **rev 45** (2026-10-31) — §6.13: a keyword argument beside a builtin's `*` binds as Python binds it, where it was refused. - **rev 43** (2026-10-31) — §6.13: a `*` before a builtin's argument spreads the collection. `zip()` gives nothing, and `format(value)` takes the empty spec. - **rev 42** (2026-10-31) — §7.1: a sentence is gone that described starting a script in a way the wire does not offer. A stored script is called from a program. - **rev 41** (2026-10-31) — §7.1: a parameter list reads a `/` and a bare `*` as Python's do, and a parameter may follow a variadic. §7.15: `raise` alone, `raise e` and `from` read as Python's do. §8.4: a method's arguments bind as a script's do. - **rev 40** (2026-10-31) — §4.1: Python's type spellings are read, and `-> None` says what no annotation says. §6.3: prefix `+`. §6.4: octal escapes, a backslash at a line's end, and any other escape keeps its backslash. §7, §7.4: an expression alone is a statement, and so is a builtin called alone, whose value is dropped; `...` and an annotation with no value do nothing. §9.3: `import a, b` and `as`. §10: an import refused past its first word is read on as Python reads one. - **rev 39** (2026-10-31) — §5, §12: a verb's word, `match`, `case`, `branch`, `commit` and `rollback` are names where their form does not stand, and only Python's keywords and `null` are reserved. §6.4, §6.4a: a string opens with either quote, a prefix `u` or `U` changes nothing, an f-string may be triple-quoted, and a field may hold a string in the other quote. §7: a `;` ends a simple statement, and a block's simple statements may stand on its header's line. - **rev 37** (2026-10-29) — §4.1: each typed value is written as its factory call, and only so. §4.2, §5.1, §5.4, §5.9, §9.5: refusal codes are snake_case, `conflicting_write`, `not_recorded`, `already_in_effect`, `derived_field` and `scope_forbidden` among them. §6.4: a typed value does not join an adjacent string. §7.8: a typed value in a `case` reads as a class pattern. - **rev 36** (2026-10-29) — §7.11, §9.3: `import` is a reserved word. §12: every keyword Python reserves is reserved, `del`, `from`, `import` and `yield` among them. - **rev 35** (2026-10-29) — §6.1: `is` is a reserved word. §12: Python's own keywords are reserved, as they are in Python, bar `del`, `from`, `import` and `yield`. - **rev 33** (2026-10-23) — §3: the natural language is set by the `language` parameter alone. §4.1: each value class that has a prefix spelling states it beside the factory call it equals. A name is bound by bare assignment; a plain call runs the body its callee had when the calling program was compiled; a constructed value exists only while the program runs and is never stored. The `known_at=` table's `DerivedField` condition names the rule whose text at that moment the store does not keep. The quick card follows the same wording. - **rev 32** (2026-10-21) — §8.8 says the three absence premises take a pair, and that a pair conclusion yields at every pair under `not_asserted` over the same names. - **rev 31** (2026-10-19) — §9.7 names the Library Reference at its new address. - **rev 30** (2026-10-17) — §4.1 says a `real` computes under all seven arithmetic operators, `+ - * / // % **`, and a `complex` under `+ - * / **`, as §6.3 states. - **rev 29** (2026-10-17) — §9.5 says that removing what a recompile no longer declares needs a `read-write-delete` key, and that under a `read-write` key such a compile is refused, names what it would remove and changes nothing. - **rev 26** (2026-10-17) — The header states Version, Status, Audience and the Markdown copy, one field to a line. Citations of the MCP Reference and the Python Specification name them so and link to the section they cite; §3 links MCP Reference §8.1 for the accepted languages. §5.7 says its table is a selection and points to MCP Reference §2 for every tool. §7.11 cites §9 for modules. `writers` and `written_by` (§11.2) name store keys. Passages that had been struck through are removed, and the page states only what holds. - **rev 21** (2026-10-14) — This section records revisions 13 to 20 as one entry. Nothing else changed. - **rev 13 to rev 20** (2026-10-13 to 2026-10-14) — *Operators* and *Modules*: new sections and examples, and the sections around them brought into line. A few arithmetic answers are correctly rounded where Python's platform rounds them wrongly, and so differ from Python's in the last digit. - **rev 4** (2026-10-11) — The MIT grant, in the opening section, is spelled licence. Cross-references take the published documents' short names, the ones the footer uses — Python Tutorial, Python Specification, MCP Reference, Modules Reference. Nothing else changed. - **rev 3** (2026-10-11) — The revision is the surface's: one number shared by every published document, the web pages and the service wire. This section says so, and the Version line moves from rev 1 to it. Nothing else changed. - **rev 1** (2026-10-11) — The Version line states the document's revision and this section is added. Nothing else changed.