# DKE Python — Language Specification **Version:** 18.2.8, changed in rev 159 · alpha · API 1.3 · rev 159\ **Status:** Published normative specification of DKE Python.\ **Audience:** Programmers writing DKE Python; authors of third-party tooling (linters, formatters, syntax highlighters, language servers, IDE plugins, alternative compilers).\ **This document as Markdown:** [dke-python-spec.md](/spec/dke/python/dke-python-spec.md) — the same text this page is rendered from, for the tooling authors above who would rather read the source than the page. --- ## 1. Status and scope This document is the **normative specification** of **DKE Python** — the language a DKE customer writes and submits to the DKE service at `dke.langsyn.net`. DKE Python is a Python-flavoured concrete surface over DKE's knowledge-operation semantics. Source files use the extension `.dpy`. The language and this specification are open under the MIT licence; the DKE service that runs DKE Python is LangSyn's proprietary product and is not open. Third parties are free to implement compilers, linters, formatters, syntax highlighters, language servers, IDE plugins, and alternative tooling against this document. A DKE client sends DKE Python source over the wire; DKE compiles, stores, and executes it. The published surface is the language; the implementation that runs it is LangSyn's product. ### 1.1 Conformance An implementation of DKE Python is **conforming** when, for every input program that this specification classifies as accepted, the implementation parses and accepts the program with the static-semantics outcomes this specification prescribes; and for every input program this specification classifies as rejected (parse_error, type_error), the implementation rejects it with the error class this specification names. Runtime outcomes (refuse, engine_error) are observable but depend on the target store's state; conformance applies to the SHAPE of those outcomes (status discriminator, transcript shape), not to the data. A third-party compiler that emits something other than what DKE emits internally is conforming as long as the **observable behaviour** matches: same input source plus same target store plus same arguments produces a transcript with the same shape and the same status discriminators across statements. The conformance keywords **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** in this document carry their RFC 2119 meanings. ### 1.2 Relationship to DKE DKE Python is the contract between a customer and the DKE service. Three documents divide that contract between them: - **this specification** — the **language**: lexical structure, grammar, type system, static and dynamic semantics, and the built-in verb surface; - the [MCP Reference](/ref/dke/mcp/) — the **transport**: the tool surface, request shapes, response envelope, error model and billing, and the wire-visible behaviour of the compile/store/run lifecycle; - the [modules chapter](/ref/dke/python/#modules) of the Python Reference — the **unit of submission**: what a module is, how `import` resolves one, and what re-submitting one does to a store. Internal storage shapes, code-generation strategies, and execution mechanisms are intentionally out of scope — they are the implementation's choices, and a conforming client must not rely on them. ### 1.3 Spec versioning This is the **DKE Python — Language Specification**, version V, changed in rev R, at **alpha · API 1.3 · rev N**, as the Version line above states it: this document's own **version**, which moves only when its text does, and the revision it last changed in; then the release it belongs to, an **alpha** release describing **API version 1.2**, at **revision** N — one number for the whole published surface, which every document, the web pages and the service wire state and which moves whenever any of them changes. §11.1 says what each promises, and Appendix E records the version and revision at which this document changed. DKE Python is a Python-flavoured surface end to end: - **Declarations use `def`.** `def audit(K: string, s: string):` — the Python function-definition shape, with a `:`-headed indented body. - **Verbs are function calls.** The built-in verb surface (§7) is written as calls, e.g. `remember(K.s.a, v, src)`, `current(K.s.a)`, `list_attributes(K)`, `print(msg)`. - **Bare assignment binds a read.** `cur = current(K.s.a)`. - **`match` / `case`.** Discrimination is spelled `match :` with `case :` arms. One arm may name ALTERNATIVES, separated by `|` — `case int | string:` — and may carry a **guard**, `case int as n if n > 0:` (§6.2). - **`try` / `except`.** Handled blocks use Python's `except [as ]:` shape; the `as ` binding is optional (`except refuse:`). - **Script invocation is a plain call.** `audit_subject(K, s)` — a program reaches a stored script by importing its module and calling it qualified ([MCP Reference §4.2](/ref/dke/mcp/#run-execute), [Python Reference §9.6](/ref/dke/python/#namespacing)). - **Word-form logical operators.** `and` / `or` / `not`. ### 1.4 What this specification does NOT define - The wire transport between the client and the target service. - The storage format used by the service to retain compiled scripts. - The internal representation of types at runtime. - DKE's error message text (only the error category is normative; message strings are quality-of-implementation). - Operational characteristics: performance, latency, quotas, throttling. ### 1.5 Independence from a live service This specification defines the conformance contract for any implementation of DKE Python and for the target service at `dke.langsyn.net`, independent of whether a given target service is reachable when this document is read. Parser/checker conformance (§12.1) does not depend on a live target. Third parties can build conforming parsers, type-checkers, linters, formatters, syntax highlighters, language servers, and IDE plugins against this specification and test them in isolation per §12.2. Compiler/runtime conformance (§12.1) is claimed against whichever target service an implementation connects to. --- ## 2. Notation Grammar productions use a small EBNF variant: - `lowercase` — non-terminal. Each one a right-hand side names has a production in this document. - `'literal'` — terminal (matched verbatim, case-sensitive). - `'a'..'z'` — any one character from the first to the last, inclusive. - `UPPERCASE` — token class (defined in §3). - `A | B` — alternative. - `A B` — sequence. - `A?` — zero or one occurrence. - `A*` — zero or more occurrences. - `A+` — one or more occurrences. - `A{n}` — exactly n occurrences; `A{m,n}` — between m and n. - `( A )` — grouping. - `<...>` — a set described in words rather than spelled out, such as the characters a literal may hold or the names another section lists. - `NEWLINE`, `INDENT`, `DEDENT` — the layout tokens the lexer emits from the off-side rule (§3.2). - `# ...` — informal note. Where lexical and syntactic productions both apply, lexical rules take precedence (tokens are recognised first; the parser consumes the token stream). --- ## 3. Lexical structure ### 3.1 Source encoding DKE Python source MUST be valid UTF-8. A conforming implementation MUST reject ill-formed UTF-8 sequences with a parse_error that names the offending byte offset (and SHOULD name the invalid byte). ### 3.2 Whitespace, line terminators, and the off-side rule DKE Python is **indentation-significant**. A line feed (U+000A), a carriage return (U+000D) and the carriage-return / line-feed pair each end a source line; the pair ends one line, not two. Every line ending reads as a line feed, including inside a triple-quoted string, so a program means the same whichever of the three its file uses. A block is introduced by a header line ending in `:` and is delimited by indentation: ``` def f(K: string): ``` - Indentation is measured in **spaces**; a tab in indentation is a lexical error (parse_error). - A **form feed** (U+000C) is whitespace between tokens, as a space is. In a line's indentation it is not counted: it sets the count back to zero, so only the spaces after it count. A line holding four spaces after a form feed is indented four, as CPython reads it, and a line holding a form feed after four spaces is not indented. - A deeper indent opens a nested block (the lexer emits `INDENT`); returning to a shallower level closes as many blocks as needed (`DEDENT`). An indent that matches no open level is a parse_error. - Only a header's `:` opens a block, so a line indented deeper below a line that does not end in one is a parse_error, "unexpected indent". It is answered where CPython answers it: at the line's first token, with that token's column counted from zero. The two lines `x = 1` and ` y = 2` are a parse_error at line 2, column 4. A line holding only a `\` after its indentation is answered on the line it continues onto. - Blank lines and comment-only lines do not affect block structure. - Inside `(`, `[` or `{`, newlines are not significant: a bracketed expression may span lines. - A **trailing comma** may follow the last item of a comma-separated list written inside `(`, `[` or `{`: the arguments of a call (to a script, a builtin, a method, a verb or a class), the parameters of a `def` or a method, the base of a `class`, the elements of a literal (§3.5.9, §3.5.11–§3.5.13) and the elements of a sequence or mapping pattern (§6.2). It means nothing: `f(1, 2,)` is `f(1, 2)`, so a list written one item per line may end every line with a comma. It never stands for an item. `f(,)`, `f(1,,2)`, `[,]` and `[1,,2]` are parse_errors, and `f(1,)` passes one argument. A subscript's comma makes a tuple, as in Python. In two bracketed forms the comma makes a tuple of one: in a subscript, where `d[1,]` is `d[(1,)]` (§4.4), so `xs[0,]` is a type_error on a list, which is indexed by an `int`; and in `(e,)`, rather than a parenthesized `e` (§3.5.11). The target list of a `for` header or a comprehension clause is not bracketed and takes one too: `for a, b, in xs:` is `for a, b in xs:`, and in `for a, in xs:` the comma makes a target list of one (§8.11), as it does in `a, = t`. - A backslash immediately before a line ending is an **explicit line continuation**: the two physical lines join into one logical line (no `NEWLINE` is emitted and the continuation line's leading spaces are not indentation). The `\` MUST be the last character on the line; a `\` followed by anything other than the line ending is a parse_error, answered one past the `\`, where CPython answers it: `x = 1 \ y` is a parse_error at column 8. So is a `\` the source ends after, with or without its line ending, since there is no line for it to join: it is refused one past the `\`, as CPython refuses it. Inside a bracket the bracket left open is the answer instead (§10.1). ### 3.3 Comments A `#` begins a comment that runs to the end of the line. Comments are equivalent to whitespace and do not affect block structure. One comment is also read: a source's **first line**, when it opens `# dke:`, is the source's declaration of what it is written in (§3.9.1). It remains a comment, and changes nothing the program means. ### 3.4 Identifiers DKE Python identifiers follow **Unicode UAX #31** with the standard profile, as Python's do: ``` identifier := XID_Start XID_Continue* # read in NFKC ``` An identifier starts with any codepoint having the Unicode `XID_Start` property and continues with any codepoint having `XID_Continue`. **Every script Unicode defines is available**, including those outside the Basic Multilingual Plane — Latin, `æ ø å`, CJK ideographs, Arabic, Hebrew, Devanagari, Thai, Armenian, Georgian, Tamil, Hiragana, Katakana, Hangul, Cyrillic, Greek and the rest, together with the decimal digits and the marks a script needs to spell its words. The `_` underscore is identifier-start by convention. **A name is read in Normalization Form KC**, as Python reads it, so every spelling of one form is one name. `x` (U+FF58) is the name `x`, `fi` is `fi`, and `e` followed by U+0301 is `é`, so `Cat` and `Cat` are one class and one kind. Where the form does not compose, the marks are part of the name: `بِسْم` and `बिल्ली` carry their vowel marks, and U+0958 is read as `क` followed by the nukta U+093C. Whether a mark composes can depend on the letter before it: `ا` followed by U+0654 is `أ`, while the same U+0654 after a letter it does not compose with stays in the name. A keyword is compared as it is written, before a name is read, as in Python. So `if = 1` binds the name `if`, and `print(if)` reads it. A word this language reads only where its form stands is compared the same way, so `match` does not begin a `match`. The name of a built-in function is read like any other: `print("p")` calls `print`. A name that keeps a combining mark from U+0300 to U+036F with no letter to compose into, as `x` followed by U+0301 does, may name a local, a parameter or an alias. It cannot name what a store holds. A class, a field, a method, a script, a rule or a constraint so named is a parse_error naming the mark, and a kind, a subject or an attribute written so in a path is a `refuse` naming it at run time. A module's name is what `import` reads: `m9` is refused, and the refusal says to write `m9`. **A string is not read**, as Python's `getattr` does not read one. A variable holding `"x"` addresses `x` where the path `K.x.v` addresses `K.x.v`, and a script sent to the service by name is found under the name it reads as: `ok9.f`, never `ok9.f`. Two limits stand where Python answers otherwise. `True`, `False` and `None` are names a program may bind, and where it has not, they are names nothing binds, told `name 'True' is not defined`, where Python finds its constants. A name does not contain U+00B7, U+0387, U+013F or U+0140, the code points CPython continues a name with; each is refused wherever a name would hold it, as an unexpected character that the message names by its code point. Identifiers are case-sensitive: `Cat`, `cat`, and `CAT` are distinct. ### 3.5 Literals #### 3.5.1 String literals ``` string-literal := '"' string-char* '"' | '"""' triple-char* '"""' # triple-quoted (may span lines) | raw-prefix '"' raw-char* '"' # raw | raw-prefix '"""' raw-triple-char* '"""' # raw, triple-quoted raw-prefix := 'r' | 'R' raw-char := | '\' # both kept: the backslash escapes nothing raw-triple-char := | '\' triple-char := | # written as itself, not escaped | string-escape # the escapes of string-char string-char := | string-escape string-escape := '\\' # backslash | '\"' # double quote | '\' # single quote | '\n' # line feed (U+000A) | '\t' # horizontal tab (U+0009) | '\r' # carriage return (U+000D) | '\a' # bell (U+0007) | '\b' # backspace (U+0008) | '\f' # form feed (U+000C) | '\v' # vertical tab (U+000B) | '\x' hex hex # one codepoint <= U+00FF, two hex digits | '\u' hex{4} # one codepoint ≤ U+FFFF, UTF-8 encoded | '\U' hex{8} # one codepoint ≤ U+10FFFF, UTF-8 encoded | '\N{' name '}' # a codepoint by its Unicode name | '\' octal octal? octal? # one codepoint, up to three octal digits | '\' # nothing: the string goes on at the next line | '\' # both kept: the backslash escapes nothing octal := '0'..'7' hex := '0'..'9' | 'a'..'f' | 'A'..'F' name := ( 'A'..'Z' | 'a'..'z' | '0'..'9' | ' ' | '-' )+ # a Unicode character name, matched case-insensitively ``` A conforming implementation MUST reject an unterminated string literal (newline or end-of-source before the closing quote) with a parse_error. **Either quote.** The productions above are written in `"`, and each is also written in `'`: a string opens with either, `'''` is the triple-quoted form as `"""` is, and it ends at the next of the quote it opened with. So `'a'` and `"a"` are one string, and the other quote is ordinary content inside it: `"it's"`, `'say "hi"'`. A prefix `u` or `U` before either quote changes nothing, as in CPython. A string held inside a collection **prints** between `'`, as CPython's `repr` does, so the printed form of a list, dict, set or tuple of strings, `['a', 'b']`, reads back as written. The `\x` escape denotes exactly two hex digits (one codepoint ≤ U+00FF); `\u` denotes exactly four hex digits (one codepoint ≤ U+FFFF); `\U` denotes exactly eight hex digits (one codepoint ≤ U+10FFFF, the only escape able to name a codepoint above the BMP). Each is encoded as UTF-8. A `\u`/`\U` value in the surrogate range `U+D800`..`U+DFFF`, or a `\U` value above U+10FFFF, is rejected. The `\N{name}` escape denotes the codepoint whose Unicode character name is `name`, encoded as UTF-8 — e.g. `\N{BULLET}` is `•` (U+2022), `\N{GREEK SMALL LETTER ALPHA}` is `α`, and `\N{CJK UNIFIED IDEOGRAPH-4E00}` is `一`. **Every** named character is recognized, across all scripts — Latin, Greek, Cyrillic, Arabic, Devanagari, the CJK ideographs, Hangul syllables, and the rest. The name is matched **case-insensitively** but is otherwise exact (interior spaces and hyphens are significant; no leading or trailing space). A name Unicode does not assign is a parse_error. **No NUL byte.** Any escape that would produce a NUL byte — `\0`, `\x00`, or a `\u` escape denoting U+0000 — is a parse_error. **Any other escape.** One to three octal digits name a codepoint, as in CPython: `\101` is `A`, and `\400` is U+0100. A backslash and a line feed are nothing, so a string goes on at the next line. A backslash before any other character keeps both: `"\d"` is the two characters `\d`, the same string as `"\\d"`. **Adjacent literal concatenation.** Two or more string literals separated only by whitespace are concatenated into a single string at compile time, with no `+` operator: ``` "long text " "split over lines" # same value as "long text split over lines" "a" "b" "c" # same value as "abc" ``` The merge is a compile-time fold — the result is indistinguishable from the spelled-out literal. An **f-string** participates, in either order and in any mixture, and the whole adjacent run folds together: `f"a" "b"`, `"a" f"{x}"`, `f"{x}" f"{y}"` and `"a" "b" f"{x}" "c"` all concatenate. A run containing an interpolation is a single interpolated string, not a chain of joins, so it costs exactly what the one f-string it replaces costs. This is the idiomatic way to write a long interpolated message across lines: ``` message = ( f"hello {name} " "and welcome" ) ``` A **typed value** — `datetime("…")`, `duration("…")`, `blob("…")`, `blob_b64("…")`, `scientific("…")` — does NOT participate, in either order: concatenation joins strings, and these are not strings. `datetime("2026-01-01T00:00:00Z") "x"` is a parse_error; render such a value with `str(…)` and join with `+`, or interpolate it in an f-string. **Triple-quoted strings.** A string may be delimited by `"""` or `'''` instead of one quote. A triple-quoted literal ends only at the next three of its quote, so it MAY span multiple lines — each embedded newline is a literal line feed (U+000A) — and a lone `"` or `""` inside `"""` is ordinary content. The escape grammar is identical to a string in one quote, and the leading newline (if the literal opens with one) is part of the value; there is no indentation stripping. The result is an ordinary `string` — the delimiter is purely lexical. An unterminated triple- quoted literal (end-of-source before the closing three) is a parse_error. A triple-quoted **f-string** (`f"""…"""`) is an f-string whose text may span lines, and so may its fields. ``` banner = """usage: run """ # a two-line string with an embedded newline quoted = """he said "hi""" # value: he said "hi — the literal ends at the # first """, so the quote before it closes nothing # and no quote survives at the end tail = """he said "hi\"""" # value: he said "hi" — escape the last quote to # keep it ``` **Raw strings.** A string literal prefixed `r` or `R` is **raw**: a backslash escapes nothing. It stays in the value together with the character after it, so `r"a\n"` is the three characters `a`, `\`, `n`, and `r"C:\path"` is the path as written. A backslash still keeps the next `"` from closing the literal — `r"say \"hi\""` holds both backslashes and both quotes — which is also why a raw literal cannot end in a single backslash: `r"a\"` is unterminated, a parse_error. A raw literal MAY be triple-quoted (`r"""…"""`), and a backslash before a line feed keeps both, as CPython does. The result is an ordinary `string`: the prefix is lexical, as the triple quote is, so a raw literal joins an adjacent run like any other string. A raw f-string is written `rf"…"` or `fr"…"`, in any case (§3.5.10). There is no bytes literal; `b"…"` and `rb"…"` are refused naming the `blob` they would denote (§3.5.8). #### 3.5.2 Integer literals ``` int-literal := dec-int | hex-int | oct-int | bin-int dec-int := dec-digit ( '_'? dec-digit )* hex-int := '0' ( 'x' | 'X' ) '_'? hex-digit ( '_'? hex-digit )* oct-int := '0' ( 'o' | 'O' ) '_'? oct-digit ( '_'? oct-digit )* bin-int := '0' ( 'b' | 'B' ) '_'? bin-digit ( '_'? bin-digit )* dec-digit := '0'..'9' oct-digit := '0'..'7' bin-digit := '0' | '1' ``` Integer literals are decimal by default; the `0x` / `0o` / `0b` prefixes (case-insensitive) select base 16 / 8 / 2. A single `_` may separate digits (`1_000_000`, `0xDE_AD`) and may also directly follow a base prefix (`0x_FF`); it may not otherwise lead, and it may not trail or double. A decimal literal with a **leading zero** and a nonzero digit is rejected (`010`, `007`) — octal requires the explicit `0o` prefix; an all-zero run (`0`, `00`, `0_0`) is the integer zero and is legal. A leading `-` is the prefix unary-minus operator (§4.4, §8.3), with one exception: `-9223372036854775808`, the smallest integer, is a literal. Its magnitude alone (`9223372036854775808`) is out of range and stays a parse_error, so the sign is part of this literal rather than an operator applied to it. The exception is exactly that one spelling — a **binary** minus is unaffected, so `1 - 9223372036854775808` remains a parse_error for its out-of-range operand. Integer values fit a signed 64-bit two's-complement range, and any other literal outside it is a parse_error. A number that breaks these rules, or those of a real (§3.5.7), is answered as CPython's tokenizer answers it. The answer comes at the character where reading stops and names the kind of literal: an invalid decimal, hexadecimal, octal or binary literal. A `_` with no digit after it is answered at the `_`, a base prefix with no digit after it at the prefix's letter, and an exponent's sign with no digit after it at the sign. A decimal digit outside an octal or binary literal's base is answered at that digit, which the answer names, so `0b12` is a parse_error at its `2`. Each is answered before a grammar error anywhere in the program (§10.1). #### 3.5.3 Boolean literals ``` bool-literal := 'True' | 'False' ``` `True` and `False` are reserved words (§3.7). #### 3.5.4 No `None`, and `null` in two positions DKE Python has no `None`, because every name holds one type (§6.1) and no type means "a value or nothing" (§5.1). A `None` would need such a type, so the language has no value that stands for absence. Each use `None` has in Python is answered without one: - **A stored value that is absent** is asserted with the `assert` verb (§7.1), which carries no value argument. The absence is a claim with a source, not a value in the cell. - **A parameter that may be omitted** takes a default of its own type: `def f(n: int = 0)`, not `def f(n: int = None)`. - **A script with no result** returns no value and ends with a bare `return`. A script that returns a value returns one on every path; a result that may be missing has no spelling. `null` is written in exactly two positions, and neither is a value a name can hold. It is an **open bound of a validity window** (§7.1), where it leaves that side of the window unbounded, and it is the pattern of a **`case null:` arm** (§5.6), which matches a window bound read back open. A program tests for an open bound by matching, not by comparing: `c.valid_to == null` is refused with a lesson that names `match c.valid_to:` with `case null:`. `None` is written in exactly one position, and it is not a value a name can hold there either: a **whole argument of `slice(...)`** (§5.3), where it leaves that bound open, as CPython's `None` does. `slice(None, 3)` is the slice CPython writes `slice(None, 3, None)`, and prints as that. Its open bound reads back as the `null` class, the class an open window bound reads back as, so `slice(None, 3).start` is matched with `case null:` and not compared. `x = None`, `f(None)` and `[None]` stay refused, and so does `slice(None if c else 1)`, where `None` is part of an argument rather than the whole of one. Both words are reserved (§3.7). Neither can be bound as a name anywhere a name is bound, and a read of either where a value goes is refused with the lesson above. `None` is reserved although it names nothing, so that a reader writing Python meets the lesson rather than declaring a name nothing can read. #### 3.5.5 Datetime literals ``` datetime-literal := 'datetime' '(' '"' rfc3339-utc '"' ')' rfc3339-utc := date 'T' time frac? 'Z' date := dec-digit dec-digit dec-digit dec-digit '-' dec-digit dec-digit '-' dec-digit dec-digit # YYYY-MM-DD time := dec-digit dec-digit ':' dec-digit dec-digit ':' dec-digit dec-digit # HH:MM:SS frac := '.' dec-digit{1,9} # 1..9 fractional-second digits ``` A datetime literal denotes an **instant in UTC**, written as the factory call `datetime("")` over an RFC 3339 timestamp — e.g. `datetime("2026-01-02T03:04:05Z")`. Its static type is `datetime` (§5.1); its value class is `datetime` on the read side (§5.6). The argument is a single string literal. The trailing `Z` (UTC designator) is required. Fractional seconds are optional and carry 1 to 9 digits. A datetime whose text is not a well-formed RFC 3339 UTC timestamp — a missing `Z`, a malformed field, or more than 9 fractional digits — is a **parse_error**. (The field ranges — month `01`..`12`, a valid day, and so on — are validated by the target service; a value that is lexically well-formed but denotes no real instant is a runtime refuse.) Two datetime values that denote the same instant are equal; a value reads back with insignificant trailing fractional zeros removed, so `datetime("2026-01-02T03:04:05.500Z")` reads back as `2026-01-02T03:04:05.5Z` and `datetime("2026-01-02T03:04:05.000Z")` as `2026-01-02T03:04:05Z`. #### 3.5.6 Duration literals ``` duration-literal := 'duration' '(' '"' iso8601-duration '"' ')' iso8601-duration := '-'? 'P' days? ( 'T' hours? minutes? seconds? )? # >= one term days := dec-digit+ fraction? 'D' hours := dec-digit+ fraction? 'H' minutes := dec-digit+ fraction? 'M' seconds := dec-digit+ fraction? 'S' fraction := '.' dec-digit{1,9} # lowest-order term only ``` A duration literal denotes a **length of time**, written as the factory call `duration("")` over an ISO 8601 duration — e.g. `duration("PT1H30M")` (one hour thirty minutes). Its static type is `duration` (§5.1); its value class is `duration` on the read side (§5.6). The argument is a single string literal: a leading `P`, an optional day term `D`, and an optional time part introduced by `T` carrying any of `H`, `M`, `S` in that order; at least one term is required. Calendar years and months are **not** accepted (they have no fixed number of seconds). A duration whose text is not a well-formed ISO 8601 duration of this subset — a missing `P`, a stray unit, units out of order, or a bare `T` — is a **parse_error**. **Sub-second durations.** The lowest-order term present may carry a decimal fraction of up to nine digits, so `duration("PT0.5S")`, `duration("PT1.5H")` and `duration("PT0.000000123S")` are all well formed. Only the last term may be fractional — `duration("PT1.5H30M")` is a **parse_error**, since a fraction on a term followed by smaller terms is ambiguous. Durations are exact to the nanosecond: `duration("PT0.1S")` is exactly one tenth of a second, not the nearest binary approximation of it. A span must fit within roughly ±292 years; a longer one is a **parse_error** rather than a silently wrapped value. A duration reads back in a **canonical** form: equal durations render identically and smaller units roll up into larger ones, so `duration("PT90M")` reads back as `PT1H30M` and `duration("PT1.5H")` does too. A sub-second remainder reads back as a decimal fraction on the seconds term with trailing zeros removed, so `duration("PT1.500S")` reads back as `PT1.5S` and a whole number of seconds carries no fraction at all. Two durations that denote the same length are equal regardless of how they were written. **Negative durations.** A leading `-` negates the whole span, so `duration("-PT2H")` is two hours *backwards*. A leading `+` is not accepted — positive is the default and the sign would say nothing. Negative spans arise naturally from arithmetic (§8.3): subtracting a later instant from an earlier one gives one, and it renders with the same leading `-`, so every value the language computes is a value the language can also write. #### 3.5.7 Real literals ``` real-literal := int-part '.' frac-part? exponent? # 3.5, 5. (trailing dot), 1.5e-3 | '.' frac-part exponent? # .5 (leading dot), .5e2 | int-part exponent # 1e5 (exponent, no point) int-part := dec-digit ('_'? dec-digit)* frac-part := dec-digit ('_'? dec-digit)* exponent := ('e' | 'E') ('+' | '-')? dec-digit ('_'? dec-digit)* ``` A real literal denotes a **fractional number**, written as a decimal with a point — `3.5`, `0.25`, a **leading-dot** form `.5` (== `0.5`), or a **trailing-dot** form `5.` (== `5.0`) — and/or an **exponent** (`e`/`E`, an optional sign, and one or more digits), e.g. `1e5` (100000.0), `1.5e-3` (0.0015), `.5e2` (50.0), `6.022e23`. A literal with an exponent is a `real` even without a decimal point (`1e5`). The literal's static type is `real` (§5.1); its value class is `real` on the read side (§5.6). An exponent that leaves the finite `real` range **upward** is the real infinity, as in Python: `1e400` is `inf`. One that leaves it **downward** underflows to zero, so `1e-400` evaluates to `0.0`. There is no literal for a NaN, as in Python; `float("nan")` and `math.nan` make one (§8.13, §8.16). A real is distinguished from an integer literal (§3.5.2) by the decimal point or the exponent: `3` is an integer, while `3.5`, `.5`, `5.`, and `3e0` are reals. A point is part of a real when it **follows** a digit (`5.`) or **precedes** one (`.5`); a point with a digit on neither side — a lone `.` — is the field/access operator (§4.4), not a real, so `.name` accesses a field. Because `5.` is a complete real, `5.name` is the real `5.0` followed by `name` (a parse error), not a field access on an integer. Likewise an `e`/`E` is part of a real **only** when followed by an optional sign and a digit — `1e5` is a real, but `1e` is an integer `1` followed by the identifier `e`. An exponent is decimal only; a base prefix (`0x`/`0o`/`0b`) never takes an exponent (`0xe5` is the hex integer 229). Underscores may separate digits for readability (`1_000.5`, `1_000e1`) and are not significant; a `_` with no digit after it is an invalid decimal literal, answered at the `_` (§3.5.2), and a `_` straight after the point, as in `1._5`, begins a name the real `1.` runs into. A real renders as its **shortest round-tripping decimal** — the same text CPython's `repr(float)` produces: **fixed-point** when the value's decimal exponent is in `[-4, 15]` (insignificant trailing zeros are dropped, so `3.50` renders `3.5` and `5.0` renders `5.0`), **scientific** (`d[.ddd]e[+-]NN`, a signed two-or-more-digit exponent) for larger or smaller magnitudes (`1e20` renders `1e+20`, `1.5e-8` renders `1.5e-08`, `1e-5` renders `1e-05`), `-0.0` for negative zero, and `inf`, `-inf` and `nan` for the two infinities and a NaN. The identical text is used on the wire, on write-through, and on read-back — there is no store-vs-display difference: a cell holds every real exactly, `-0.0`, the infinities and a NaN included. A real is an **expressible** value: it renders in `print` and via `str(…)`. Reals are **computable**: `+`, `-`, `*`, `/`, `//`, `%`, and `**` accept real operands, and the ordered comparisons `<`, `>`, `<=`, `>=` accept them too (§8.1, §8.3). The numeric operators **promote**: an operation with an `int` and a `real` operand produces a `real` (`1 + 0.0` is `1.0`), the exact value of the operation rounded once (§8.3). `/` (true division) always produces a `real`, even for `int / int` (`7 / 2` is `3.5`); the floored integer quotient is `//` (§8.3). Only arithmetic promotes, and `float(x)` converts on request (§8.13). Nothing else changes a number's class. A value handed to a declared `real` — assigned, passed, defaulted or returned — **fills** it and keeps its class (§6.4). A construct that selects one of two values — a conditional expression (§8.10), `and` / `or` (§8.2), `min` / `max` of separate values — **joins** their types. An `int` / `real` pair is typed `real`, a pair holding a `bool` is a `value` holding a number (§5.6), and the value is the one selected, unconverted, as in Python: `1 if True else 2.5` is `1`. So a `real` can hold a value whose class is `int`. A `real` field written with one holds `1` where arithmetic would have written `1.0` (§13.3, Appendix C.9). The construct that produced the number decides which, not the declaration, and the two compare equal (§8.1). A real arithmetic result whose magnitude leaves the finite range is `inf` or `-inf`, as Python answers it, and an operation with an infinite or NaN operand answers IEEE 754's value, `nan` where there is none (§8.3). Real division by zero is a runtime refuse (§10.3), as in Python. An `int` result past 64 bits is a refuse too (§8.3); an `int` has no infinity. #### 3.5.8 Blob literals ``` blob-literal := hex-blob | b64-blob hex-blob := 'blob' '(' '"' ( hex-digit hex-digit )* '"' ')' b64-blob := 'blob_b64' '(' '"' base64-char* '"' ')' hex-digit := dec-digit | 'a'..'f' | 'A'..'F' base64-char := 'A'..'Z' | 'a'..'z' | dec-digit | '+' | '/' | '=' ``` A blob literal denotes an **opaque byte string**. It has two input spellings that denote the same byte space; the static type is `blob` (§5.1) either way, and its value class is `blob` on the read side (§5.6). - **Hex** — an even number of hexadecimal digits, written `blob("")` — e.g. `blob("48656C6C6F")` (five bytes). An empty `blob("")` is a valid zero-byte blob. Each byte is exactly two hex digits; an odd digit count, or any non-hex character, is a **parse_error**. - **Base64** — RFC 4648 base64, written `blob_b64("")` — e.g. `blob_b64("SGVsbG8=")` (the same five bytes as `blob("48656C6C6F")`). The content is a multiple of four characters drawn from `A`–`Z`, `a`–`z`, `0`–`9`, `+`, `/`, with zero to two trailing `=` padding characters; an empty `blob_b64("")` is a valid zero-byte blob. A wrong length, a non-alphabet character, or misplaced padding is a **parse_error**. Base64 is a convenience input spelling for pasting base64-encoded payloads; it produces exactly the bytes it encodes. Regardless of the input spelling, a blob **reads back** at the `blob` value class in a single canonical uppercase-hex form (so a value written `blob_b64("SGVsbG8=")` reads back `48656C6C6F`). A blob is **one value however it was written**: the two spellings and a read of the same bytes are equal by their bytes wherever blobs compare — `==` and `!=`, `in`, `.count`, `.index`, a set's elements and a dict's keys — so `blob_b64("AQI=") == blob("0102")` is `True` and `len({blob_b64("AQI="), blob("0102")})` is `1`. A blob is an **expressible** value: it renders as that hex text in `print`, via `str(…)` and in a write's transcript line, whichever spelling produced it (`print(blob_b64("AQI="))` prints `0102`). A write's transcript line gives the hex whole up to 65,536 characters; past that it gives the length and the first 8 bytes, marked as shortened ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). `print` and `str(…)` are never shortened. `len(b)` is its count of bytes (§5.2), and `struct` lays numbers out as one and reads them back (§8.18); it is cut, joined and read byte by byte as Python's `bytes` are (below), and is not otherwise operated on. **Slice, join and byte.** A blob is cut, joined and read byte by byte as Python's `bytes` are, each form one operation. `b[i:j]` and `b[i:j:k]` answer the blob of the bytes selected, in order: a bound left out is open, a negative bound counts from the end, a bound past either end is the end, a range that is empty or inverted is the empty blob, and a negative step walks from the high end, so `b[::-1]` is the blob reversed and `b[::2]` is every other byte. A step of `0` is the catchable refusal `slice step cannot be zero` (`slice_step_zero`). A slice value `s` selects as its bounds do, `b[s]` being `b[s.start:s.stop:s.step]`. `b1 + b2` is the blob that holds the bytes of `b1` and then those of `b2`. `b[i]` is the byte at position `i` as an `int` from 0 to 255, a negative `i` counting from the end, and a `bool` position being the int it is. A position past either end is the catchable refusal `index out of range` (`index_out_of_bounds`), and an index of another type than `int` is a `type_error` at compile, in Python's words, `byte indices must be integers or slices, not str`. The answers are those of this Python, over the `bytes` that hold the same bytes: ```python def blob_slice(b, start, stop, step): return b[start:stop:step] def blob_join(b1, b2): return b1 + b2 def blob_byte(b, i): return b[i] ``` with `None` for a bound or a step left out. `blob("000102030405")[1:4]` is `blob("010203")`, `blob("0001")[1]` is `1`, and `blob("00FF") + blob("01")` is `blob("00FF01")`. A blob is not a list, so none of the three builds cells, and each is one operation however long the blob is, as every built-in operation is; a join is held as any value is, and a run that holds more than its limit allows is refused as for a string. **No bytes literal.** A Python bytes literal — `b"…"`, or `rb"…"` / `br"…"` in any case — has no counterpart, because a byte string is a blob. It is a parse_error whose message names the blob it denotes, read with CPython's bytes escapes: `b"abc"` is `blob("616263")`, and `rb"a\n"` is `blob("615C6E")`. A literal whose bytes cannot be read without guessing is told both spellings instead: one holding a character outside ASCII, which CPython refuses too, or a triple-quoted one. #### 3.5.9 List literals A list literal builds a **constructed list** — a finite, ordered sequence of element values, evaluated as the program runs: ``` list-literal := '[' ( display-item ( ',' display-item )* ','? )? ']' display-item := expr | '*' bitor-expr # an element, or a spread (§8.15) ``` The elements are arbitrary expressions, evaluated left to right; a trailing comma is permitted (`[1, 2,]`); the empty list is `[]`. Lists nest — a list may contain lists (`[[1, 2], [3]]`). The element type is inferred (§5.1): a list whose elements share a type is a `list`; a list mixing scalar value classes is a `list` — one mixing only `int`s and `real`s holds **numbers**, which the numeric operators and folds take while each element keeps its own class (§5.6: `[1, 2.5][0]` is `1`, and `sum([1, 2.5])` is `3.5`), and one mixing a `scientific` with numbers holds **magnitudes**, which order (§5.6); a list mixing a `complex` with numbers is a `list` (§3.5.15); the empty list is `list`, refined by context. Lists (or tuples, sets, dicts) whose element types meet (§8.1) are one element type, so `[[1], [2.5]]` is a list of lists holding numbers. A literal that mixes a scalar with a non-scalar (`[1, [2]]`), or collections whose elements do not meet (`[[1], ["a"]]`), has no common element type and is a type_error. A constructed list exists only while the program runs: it holds a finite number of elements and is never stored. Unlike a `string` it is **shared** and changes in place — every name holding it sees a change made through any of them (§8.14). It is distinct from the store-derived finite collections of §5.1 (which are produced by read verbs); the two share the read surface (indexing, slicing, `.length`, `for … in`, membership, and the value-search methods `xs.index(v[, start[, end]])` → the first index of `v` within an optional codepoint window and `xs.count(v)` → the number of occurrences — `index` of an absent value is a catchable refuse, §10.3) but a constructed list additionally renders in `print` and `+` (§8.4, §8.7), supports `==`/`!=` (§8.1) and comprehensions (§8.9), and is copied with `xs.copy()` (§8.14). #### 3.5.10 Formatted string literals (f-strings) A formatted string literal — an **f-string** — is a string literal carrying a lowercase or uppercase `f` prefix (`f"…"` / `F"…"`) whose `{ … }` sections are **replacement fields**: an expression written between braces is evaluated and its value is written into the string in place of the field. ``` fstring-literal := fstring-prefix '"' ( fstring-char | replacement-field )* '"' fstring-prefix := 'f' | 'F' | raw-prefix ( 'f' | 'F' ) | ( 'f' | 'F' ) raw-prefix # raw, §3.5.1 fstring-char := string-char (§3.5.1) # '{{' and '}}' denote literal '{' and '}' replacement-field := '{' expr '='? ( '!' ('r' | 's') )? ( ':' format-spec )? '}' format-spec := ( fill? align )? sign? 'z'? '#'? '0'? width? grouping? ( '.' precision )? format-type? # §8.13 fill := align := '<' | '>' | '^' | '=' sign := '+' | '-' | ' ' width := dec-digit+ grouping := ',' | '_' precision := dec-digit+ format-type := 's' | 'd' | 'x' | 'X' | 'o' | 'b' | 'c' | 'n' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | '%' ``` The literal portions accept the same escapes as an ordinary string literal (§3.5.1). A doubled brace `{{` or `}}` denotes a single literal `{` or `}`. Each replacement field holds a full expression (§4.4) — a bound variable, a literal, an arithmetic expression, a built-in method call, an index or slice, or a field read. The value of an f-string is exactly the value of the equivalent `+` concatenation of its literal parts and its replacement-field expressions, in order: `f"a{x}b"` has the value `"a" + str(x) + "b"`, and a field is written using the same rendering `+` gives that value (§8.7) — an `int`/`real`/`bool`/list is rendered as `+` renders it. A field whose value is a store read must first bind it to a name (§4.3), the same rule the `+` operator follows. An f-string is always of type `string`, including when it is a single replacement field (`f"{n}"` is a `string`, not the type of `n`). A replacement field may carry a **conversion** and/or a **format spec**: `{x!r}` renders `repr(x)`, `{x!a}` renders `ascii(x)` (and `{x!s}` renders `str(x)`, the default); `{x:spec}` renders `format(x, "spec")` — the format mini-language of §8.13 (`f"{x:.2f}"`, `f"{n:>5}"`, `f"{s:*^8}"`), and the two combine as `{x!r:>10}`. A replacement field may end its expression with a **self-documenting `=`** (`f"{x=}"`): the field writes the field's source text from just after `{` up to and **including** the `=` **verbatim** (surrounding whitespace preserved), followed by the value. With no conversion or format spec the value defaults to `repr` (not `str`) — `f"{x=}"` is `"x=5"`, `f"{ x = }"` is `" x = 5"`, and `f"{s=}"` is `"s='hi'"` — while a `:format-spec` or `!conversion` overrides that default (`f"{x=:.2f}"` is `"x=5.00"`, `f"{x=!s}"` is `"x=5"`). A trailing comparison operator (`==`, `>=`, `<=`, `!=`) is NOT the debug `=`. A field may hold a **string literal** in the quote that does not enclose the f-string, as `f"{d['k']}"` does and `f'{d["k"]}'`. The quote that encloses it ends the f-string, so `f"{\"a\"}"` is a parse_error, as in CPython. An **empty** field `{}` is a parse_error, and so is a `#` in a field's expression (`f"{x # c}"`), as in CPython. A format spec may hold **fields of its own** (`f"{x:>{width}}"`): each is replaced by its value, rendered as a field of the string is, before the spec is applied, so with `width = 5`, `f"{1:>{width}}"` is `" 1"`. A field in a spec may carry a conversion and a format spec of its own, but one whose spec holds a field is a parse_error, nested too deeply, as in CPython. A conforming implementation MUST reject an unterminated f-string (newline or end-of-source before the closing `"`) with a parse_error. A string's `.format` and `.format_map` (§5.3) and `%` with a string on its left (§8.3) format as Python's do. A template is a value, so what each asks of its arguments is asked as the program runs. #### 3.5.11 Tuple literals A tuple literal builds a **constructed tuple** — a finite, ordered, immutable sequence whose positions may each hold a different type, evaluated on the client side: ``` tuple-literal := '(' ( display-item ( ',' display-item )* ','? )? ')' # a `*` item alone needs its comma (§8.15) ``` A comma distinguishes a tuple from a parenthesized expression: `(e)` groups, `(e,)` is a one-element tuple, `(a, b)` a two-element tuple, and `()` the empty tuple. A group of one operand is that operand, and a call, an index or a `.name` after it reads it as it reads the operand alone, as in CPython: `(c).foo()` calls the method of the instance `c` holds, `(f)(x)` calls `f`, and `(xs)[0]` is `xs[0]`. Where a statement takes a value — after `return` and on the right of an assignment or an element write (`expr-list`, §4.3) — the parentheses may be left off, as in CPython: `return 1, 2` returns `(1, 2)`, `x = 1, 2` and `xs[0] = 1, 2` bind it, and `x = 1,` binds the one-element tuple `(1,)`. In an argument list or a literal a comma separates items, so a tuple there keeps its parentheses. The element type is inferred per position (§5.1): `(1, "a", True)` is a `tuple`; tuples nest. Unlike a list, a tuple's positions may differ in type, so it is indexed by a **constant** integer (§5.3) — `t[0]` has the type of the first position — and it is written after `for … in` when those positions all share one type, all hold numbers of the two classes (`(1, 2.5)`, §5.6, §6.3), or all hold class instances, which the loop variable takes at their nearest common base, as a list display's elements join (§13.8). A constructed tuple exists only while the program runs: bounded at construction, immutable, and never stored. It renders in `print`, supports `==`/`!=` (§8.1), concatenates with another tuple via `+` (§8.4) and repeats by a literal `int` count via `*` (§8.3) — the result keeping each operand's positions and their types, so a constant index into it reads the position it names. It is searched by the value-search methods `t.index(v[, start[, end]])` → the first position holding `v`, within an optional window whose bounds resolve as a slice's do, and `t.count(v)` → the number of positions holding it — wherever `v in t` is allowed. Each position is compared with `v` as `==` compares them (§8.1), and a position of another type is simply unequal, so a record is searched for any of its fields: `(1, "a").index("a")` is `1`. `index` of an absent value is a catchable refuse (§10.3) whose reason is Python's `tuple.index(x): x not in tuple`, discriminator `value_not_in_tuple`. #### 3.5.12 Dict literals A dict literal builds a **constructed dict** — a finite mapping from hashable keys to values, kept in **insertion order**, evaluated on the client side: ``` dict-literal := '{' ( dict-item ( ',' dict-item )* ','? )? '}' dict-item := expr ':' expr | '**' bitor-expr ``` The empty dict is `{}`. A key must be **hashable**: a scalar value class (string / int / bool / datetime / duration / real / blob / scientific / complex), a `value`, or a tuple whose positions are recursively hashable (§5.1); a list, dict or set is unhashable. Keys are homogeneous (a mix of scalar value classes gives a `value`-typed key part); any other mix of keys — a tuple beside a scalar, as in `{(1, 2): 1, "a": 2}`, or tuples whose positions hold types that do not join — is a type_error naming the two types, since a `value` holds a scalar (§5.6), where CPython builds the dict, and a key written into a dict, `d[k] = v`, is held to the same rule. The values join as a list literal's elements do (§3.5.9): a mix of scalar value classes gives a `value`-typed value part, holding numbers when every value is an `int`, a `real` or a `bool` (§5.6); collections whose elements meet are one type, so `{"a": [1], "b": []}` is a `dict>`; and instances of several subclasses take their nearest common base (§13.8). The inferred type is `dict` (§5.1): `{"a": 1, "b": 2}` is a `dict`. When a key is written twice, the entry keeps its first position but takes the last value. A key is matched by value, as `==` compares (§8.1), so an `int` and a `real` of equal value are **one** key, as in Python: `{1: "a", 1.0: "b"}` is `{1: 'b'}`, and `1.0 in {1: "a"}` is `True`. So are a `bool` and the `int` it is: `{True: "a", 1: "b"}` is `{True: 'b'}`. The keys of a dict whose keys are `int`s and `real`s hold numbers in the sense of §5.6, so `sum`, `min`, `max` and `sorted` fold them: `sum({1: "a", 2.5: "b"})` is `3.5`. A dict is read by key `d[k]` (a key absent at run time is a catchable refuse, §10.3), tested with `k in d` (§8.1), walked with `for k in d` over its keys (§6.3), measured with `.length`, viewed through `.keys()` / `.values()` / `.items()` (§5.1, §8.6), and copied with `.copy()` or `dict(d)` (§8.14). A constructed dict exists only while the program runs, is shared and changed in place as a list is (§8.14), and is never stored; it renders in `print`. `+` is **not** defined over dicts, the same as in Python. A `**d` entry gives every entry of the dict `d` holds, in its order, at that place among the others, as CPython's does, so a key written twice follows the rule above: `{**d, "a": 0}` keeps `0` for `"a"` and `{"a": 0, **d}` keeps what `d` holds for it. Its operand is read before the program runs, and one that is no dict is a type_error in CPython's words, as in `'int' object is not a mapping`. `d | e` (§8.3) is the same merge as `{**d, **e}`. #### 3.5.13 Set literals A set literal builds a **constructed set** — a finite collection of unique values, evaluated as the program runs: ``` set-literal := '{' display-item ( ',' display-item )* ','? '}' ``` A `{ … }` with no colon is a set (a `{ … }` with a colon is a dict, §3.5.12); because the empty braces `{}` are the empty dict, the empty set has no literal form. It is written `set()`, as in Python, or `set([])` (§8.13). An element must be **hashable**: a scalar value class, a `value`, or a tuple whose positions are recursively hashable (§5.1); a list, dict or set is unhashable. Elements are homogeneous (a mix of scalar value classes gives a `set`, holding numbers when every element is an `int`, a `real` or a `bool`, §5.6); any other mix — a tuple beside a scalar, as in `{1, (1, 2)}`, or tuples whose positions hold types that do not join, as in `{(1, "a"), (2, 3)}` — is a type_error naming the two types, as a list literal's is, since a `value` holds a scalar, where CPython builds the set. Duplicates are removed. An `int` and a `real` of equal value are duplicates, as in Python, and the one written first is kept: `{1.0, 1}` is `{1.0}`. So are a `bool` and the number it is: `{True, 1, 1.0}` is `{True}`. So are `-0.0` and `0.0`, which are equal although they render apart: `{-0.0, 0.0}` is `{-0.0}`. The inferred type is `set` (§5.1). A set renders and is walked (`for x in s`) in a **deterministic sorted order** — this is a defined refinement of Python, whose set order is unspecified. It is the order `sorted(s)` gives (§8.6): numbers rank by value, an `int`, a `real` and a `bool` together, so `{2.5, 1, 3}` renders `{1, 2.5, 3}`, `{0.5, 5, True}` renders `{0.5, True, 5}`, and `list({2, 1.5})` is `[1.5, 2]`, as Python prints them. A `scientific` ranks among the numbers by its magnitude (§8.1). Where it ties a number, the number comes first, and two `scientific`s that tie come in the order of the values they write exactly, then with fewer significant figures first, so `{scientific("6.0220e23"), scientific("6.022e23")}` renders `{6.022e23, 6.0220e23}` whichever was written first. A `complex` has no order (§8.1), so a set lists its `complex`es by real part and then by imaginary part, which is a listing order only: `{2 + 1j, 2j, 1 + 5j}` renders `{2j, (1+5j), (2+1j)}`. A `complex` and a number that are equal are duplicates, like any two equal numbers: `{1, 1 + 0j}` is `{1}`. Python's own order is not a sort and can differ elsewhere (`{-1, 0}` prints `{0, -1}` there). A set is tested with `x in s` (§8.1), measured with `.length`, compared by inclusion with `<=` / `<` / `>=` / `>` and `.issubset` / `.issuperset` / `.isdisjoint`, and combined with `|` / `&` / `-` / `^` and `.union` / `.intersection` / `.difference` / `.symmetric_difference` (§8.6), and copied with `.copy()` (§8.14). A constructed set exists only while the program runs, is shared and changed in place as a list is (§8.14), and is never stored. #### 3.5.14 Scientific literals ``` scientific-literal := 'scientific' '(' '"' scientific-numeral '"' ')' scientific-numeral := '-'? mantissa ( 'e' | 'E' ) '-'? dec-digit+ mantissa := dec-digit+ ( '.' dec-digit+ )? # a nonzero magnitude ``` A scientific literal denotes a **number carrying its significant figures**, written as the factory call `scientific("")` in scientific notation — e.g. `scientific("6.022e23")`. Its static type is `scientific` (§5.1); its value class is `scientific` on the read side (§5.6). The argument is a single string literal: an optional leading `-`, a mantissa (digits with an optional decimal point) carrying at least one nonzero digit, then a **required** exponent — the letter `e` (or `E`), an optional sign, and one or more digits. The exponent is mandatory: it is what distinguishes a scientific value from a `real` (§3.5.7) and what makes the number of significant figures unambiguous. A numeral with no exponent or with an all-zero mantissa is a **parse_error**; a magnitude too large to represent is refused at write. Significant figures are part of the value. `scientific("6.022e23")` (four significant figures) and `scientific("6.0220e23")` (five — the trailing zero is significant) denote the **same magnitude but are distinct values**: each reads back with its own figures, and writing one to a cell does not replace the other. This is the one property a `real` cannot carry — a `real` is a floating-point number that forgets how many figures were written. A scientific reads back in a **canonical** form: the mantissa is normalized to a single nonzero leading digit with the significant figures preserved, so `scientific("60.220e21")` reads back as `6.0220e22`. Numerals that normalize to the same figures are the same value regardless of how they were written. #### 3.5.15 Complex literals ``` imag-literal := ( dec-int | real-literal ) ( 'j' | 'J' ) ``` An imaginary literal denotes a **complex number whose real part is zero**, written as a decimal number followed directly by `j` (or `J`), as in Python: `4j`, `1.5j`, `.5j`, `5.j`, `1e3j`. Its static type is `complex` (§5.1); its value class is `complex` on the read side (§5.6). There is no literal for a complex with a real part. `3+4j` is the **expression** `3 + 4j`, an `int` added to an imaginary literal (§8.3), and that is how such a number is written, here as in Python. Only a **decimal** number takes the suffix. A base prefix never does: `0x1j` is not a literal. The leading-zero rule of §3.5.2 does not reach an imaginary literal either, because its digits are never read as octal, so `07j` is `7j`, as in Python. Underscores separate digits as they do in a real (`1_0j` is `10j`). An imaginary part that leaves the finite `real` range upward is infinite, as `1e400` is (§3.5.7): `1e400j` is `infj`. A `complex` holds two `real`s, its **real part** and its **imaginary part**, read with `.real` and `.imag` (§5.2). It renders as CPython's `repr` of a complex does. When the real part is a positive zero the imaginary part stands alone: `4j`, `1.5j`, and `0 - 4j` is `-4j`. Otherwise both parts are written, in parentheses, the imaginary part with its sign: `(3+4j)`, `(3-4j)`. Each part is written as a `real` is (§3.5.7), except that a whole number drops its `.0`: `3.0 + 4j` is `(3+4j)`, `1e16 + 1.5j` is `(1e+16+1.5j)`. A part may be infinite or a NaN, as a `real` may, and is written `inf`, `-inf` or `nan`, a NaN always with `+` before it: `(inf+nanj)`, `infj`, `(nan+1j)`. The same text is used by `print`, by `str(…)`, in an f-string, on the wire and on read-back. The sign of a zero part is kept through a computation and shown, as in Python: the prefix `-` negates **both** parts, so `-4j` renders `(-0-4j)` and `-0j` renders `(-0-0j)`. A cell holds each part exactly (§3.5.7), so a `-0j` written to a cell reads back as `(-0-0j)`. Two complexes that differ only in the sign of a zero part are one value to the store, as they are to `==`, so writing the other one of them to a cell that holds one adds no claim. Complex numbers are **computable**: `+`, `-`, `*`, `/` and `**` take a `complex` beside a `complex` or a number and answer a `complex` (§8.3), `abs(z)` is its magnitude and `z.conjugate()` its conjugate (§8.6). They are **equatable**: `==` and `!=` compare both parts, and a `complex` equals a number exactly when its imaginary part is zero and its real part equals that number, so `1 + 0j == 1` is `True` (§8.1). They have **no order**: `<`, `>`, `<=` and `>=` refuse one, as Python does, and so does everything that ranks its operands. A `complex` is hashable (§3.5.12), and is falsy exactly when both parts are zero (§8.2). A value handed to a declared `complex` — assigned, passed, defaulted or returned — **fills** it and keeps its class, as a number handed to a declared `real` does (§6.4): after `z: complex = 1`, `z` prints `1`, as in Python. A construct that selects one of two values (§3.5.7) **joins** a `complex` and a number to `complex`, and the value is the one selected, unconverted: `2j if c else 1` is typed `complex` and is `1` when `c` is false. Everything that takes a `complex` takes the number it may hold and answers as Python does for that number: its `.real` is the number, its `.imag` a zero of the number's class, its `.conjugate()` the number. A **collection** joins them the same way. `[1, 2j]` is a `list` whose elements keep their classes, so `[1, 2j][0]` is `1`, `sum([1, 2j])` is `(1+2j)` and `[1, 2j][1] * 2` is `4j`, as in Python. So do a set, a dict's keys and values, a tuple iterated over, `+` of two lists, set union and a nested display: `[[1], [2j]]` is a list of `list`. It still has no order, so `sorted([1, 2j])` is a type_error. A complex number reaches what the engine derives in three places. A **computed field** declared `complex` derives one by arithmetic over the class's fields (§13.7). A **standing rule** tests a field against one with `==` and `!=` and concludes one (§14.2, §14.3), and a **hypothesis** supposes one (§15). A rule and a hypothesis write it as a `case` label does (§6.2), the one form that is a literal where the rest of a program writes the expression. ### 3.6 Operators and punctuation ``` ( ) [ ] . , : = == != < > <= >= + - * / // % ** | & ^ << >> ~ ``` The tokens `|`, `&` and `^` are the bitwise operators on two integers and the set operators on two sets (§8.3, §8.6), and `|` also separates the alternatives of a `case` label (§6.2). `<<` and `>>` are the shifts, and the prefix `~` is bitwise inversion (§8.3). A `@` that begins its line begins the decorator of a rule or a constraint. After anything on its line it is Python's matrix product, which reads and which no value here answers (§8.3). The token `=` appears in bare assignment (§4.3); a class field is written with a `remember` / `update` verb call (§13.3), not `=`. The token `:` ends a block header and separates a parameter or field name from its type. A newline or a `;` ends a simple statement (§3.2, §4.3). The word forms `and` / `or` / `not` are the logical operators (§8.2); there are no `&&` / `||` / `!` tokens. ### 3.7 Reserved words The following identifiers are reserved and MUST NOT be used as user identifiers (declaration, class, method and field names, parameter names, loop variables, handler bindings, assignment and unpack targets, and names a `case` pattern binds). They are Python 3.11's keywords and the absence word `null` (§3.5.4). A conforming implementation MUST treat every one of them as reserved at the lexical level. ``` and as assert async await break class continue def del elif else except False finally for from global if import in is lambda None nonlocal not null or pass raise return True try while with yield ``` This list is complete; no other word is reserved. Every keyword Python 3.11 reserves is in it. Compiling a module, forgetting a script and reading a script's metadata are wire operations ([MCP Reference §9.2](/ref/dke/mcp/#scripts-address-by-module.script)), not statements of a program body. **This language's own words are names.** The head-words of the built-in verb surface (§7), `match`, `case`, `branch`, `commit`, `rollback` and `script` are ordinary names, as they are in Python, wherever the form each heads does not stand. A verb's word is the verb immediately before its `(`. `match` heads a `match` statement at the head of a line that ends in `:`, and `case` an arm at the head of a line in that statement's block. `commit` and `rollback` are the statement alone on a line or after a `:` or `;`, and `branch` is the context manager before its `(`. So `get = 1`, `def f(match: int):`, `case = 2`, `import list` and `xs.case(1)` are all read as Python reads them. A `def`, a `class` or an import at the top level of a unit takes the word for the whole unit, so a call of it is a call of what the unit declares. **After a `.`,** an identifier-shaped token is parsed as a field name; whether the field is meaningful for the base type is decided by the typechecker (§6). A word in the list above, which CPython reserves too — `if`, `class`, `True`, `None` and the rest of its keywords — names no field, method or path segment, as CPython's grammar reads only a name after a `.`: `xs.if`, `g(xs).class` and `remember(K.s.class, 1, "op")` are parse_errors at the word (§10.1), and so is such a word beginning a path, `current(class.s.a)`, bar `lambda`, which begins a lambda there as it does in Python and is answered where its header stops (§8.8). A kind, subject or attribute spelled as one is still addressed, through a `string` name that holds it (§6.7): `k = "class"`, then `remember(K.s.k, 1, "op")`. The type names of §5.5 (`string`, `int`, `claim_list`, …) are contextual identifiers, not reserved words; programs SHOULD nonetheless avoid them as user identifiers. ### 3.8 Token classes | Class | Examples | |-------|----------| | `IDENT` | `Cat`, `mittens`, `audit_subject`, `猫` | | `STRING` | `"orange"`, `"op1"` | | `INT` | `42`, `0`, `9223372036854775807` | | `REAL` | `3.5`, `.5`, `1e5` | | `IMAG` | `4j`, `1.5j`, `1e3j` (§3.5.15) | | `KEYWORD` | any reserved word from §3.7 | | `NEWLINE` / `INDENT` / `DEDENT` | layout tokens from the off-side rule (§3.2) | | punctuation/operators | as listed in §3.6 | | `EOF` | end of source | The typed values of §3.5.5–§3.5.8 and §3.5.14 — `datetime("…")`, `duration("…")`, `blob("…")` / `blob_b64("…")` and `scientific("…")` — have **no token class of their own**: each lexes as an `IDENT`, punctuation and a `STRING`, and becomes a literal in the grammar (§4). ### 3.9 Natural language The natural language a source is written in — which keyword vocabulary its statements use, and the language its responses are rendered in — is set by the **`language` parameter** supplied when a source is submitted. When the parameter is omitted, the language is the one the source declares (§3.9.1), and when it declares none, **English** (`eng`). `eng` is the default. The parameter's accepted values belong to the wire contract and are listed in the MCP Reference; a value outside that set is declined before the source is compiled. **This specification describes the English surface.** Every keyword, verb and rendered response given here is the `eng` spelling. The natural language is a property of how you are talking to the service, set per call, and the call decides it. A source may say what it was written in, so that a file read later carries that with it; the call is checked against what it says (§3.9.1), and it never decides in the call's place. #### 3.9.1 The first-line declaration A source MAY declare, on its **first line**, the edition and the natural language it is written in, each with an optional version: ``` # dke: edition=python.1 language=eng.1 def hello(): print("written in English, at API 1") ``` The line is a comment (§3.3), so a declared source is valid DKE Python wherever nothing reads the declaration. ``` declaration := '#' decl-blank* 'dke:' decl-blank* decl-setting decl-blank+ decl-setting decl-blank* decl-setting := decl-key '=' decl-name ('.' decl-version)? decl-key := 'edition' | 'language' decl-name := ('a'..'z')+ decl-version := ('1'..'9') ('0'..'9')* decl-blank := ' ' | ``` The two settings name different keys, in either order, and the line ends at its line terminator (§3.2). - **The edition** is `python`, DKE Python. Its version is the **API's MAJOR**, the number before the dot of the API version this document's Version line states (§11.1), so `python.1` is DKE Python at API 1.0 and at every later 1.N: a MINOR adds and changes nothing a source written at 1.0 relies on. - **The language** is a tag the `language` parameter accepts. Its version is the version of that language's **vocabulary**: the reserved words, soft keywords, type names and other words a source spells in that language. Each language counts its own. A language's version moves when one of those words is removed or respelled, and never when one is added, so a source written at a version stays valid at it as words are added. `eng.1` is the English vocabulary this specification describes. A conforming service reads the declaration so: 1. **Only the first line is read.** A first line that does not open with `#`, optional blanks and `dke:` declares nothing, and a declaration on any later line is an ordinary comment. 2. **A first line that opens so and does not match `declaration` is refused**, and the refusal shows the form. `latest` is not a version. 3. **A declared name supplies the parameter a call omits**, `edition` or `language`, and is checked as that parameter would be. 4. **A parameter that names another value than the source declares is refused.** Neither overrides the other. 5. **A version that is written must be the one the service serves**, or the source is refused, and the refusal names the version served. For the edition, that is the MAJOR of the API the service serves. A version left out is the one the service serves. Each refusal is made before the source is compiled, as a parameter outside the accepted set is declined. A file meant to be compiled later, by a service newer than the one it was written for, SHOULD write both versions: a version left out is read as whatever that later service serves. --- ## 4. Grammar ### 4.1 Programs ``` source := option-verb NEWLINE | module-body module-body := docstring? top-item+ docstring := STRING NEWLINE # documents what it opens; nothing runs it top-item := decl | class-decl | rule-decl # decl §4.2, class-decl §13.1, rule-decl §14 | constraint-decl # constraint-decl §14.10 | import-stmt | stmt # import-stmt §4.3, stmt §4.3 (incl. the four writes) — see Modules option-verb := '--' IDENT IDENT? ``` A source is the **module body** ([Python Reference §9.1](/ref/dke/python/#a-.dpy-file-is-a-module)) from its first line. Its body admits, at the top level, not only declarations but also `import` statements ([Python Reference §9.3](/ref/dke/python/#import)), data declarations (write statements at module scope, [Python Reference §9.2](/ref/dke/python/#data-declarations-at-module-scope)), and the other statements legal in a `def` body (§4.3). The module body runs once, top to bottom, when the module is imported ([Python Reference §9.1](/ref/dke/python/#a-.dpy-file-is-a-module)); a file whose body is exactly one `def` is the common single-script case. Because it runs again on every import, a module's top level writes each cell once per source and period: a second write to one cell from one source over an overlapping window refuses ([Python Reference §9.5](/ref/dke/python/#re-submitting-reconciles)). The natural language the source is written in — the language of the knowledge it names and of its authored text — is set by the call's `language` parameter, which the source's first-line declaration may supply (§3.9.1); it also selects the language in which the script's own structured output — its invocation transcript ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) — is rendered. It does not select the response language of the surrounding service surface, which remains a property of the calling channel; the two may differ. **Option verbs.** A `--`-prefixed word in declaration position is an **option verb** — a compiler-directed verb, addressed to the implementation rather than the store. An option verb's spelling involves no language keyword, so `--help` means the same thing in every context. The supported set is `--help` (§4.2), which takes an optional topic word; an unknown option verb is a parse_error whose diagnostic names the supported set. An option verb never compiles and never stores anything, by construction. A source file may declare one or more scripts — a **compile unit**. Every declaration compiles and stores under its own name. A declaration may call any other declaration of the same unit, above or below it, or an already-stored script, as long as no call leads back to its caller (§6.8, §9.7, compile-before-call). Two declarations in one unit MUST NOT share a name. A unit need not declare a script at all: a module whose body only installs a schema, defines a rule, or writes data is valid ([Python Reference §9.1](/ref/dke/python/#a-.dpy-file-is-a-module), [§9.7](/ref/dke/python/#published-modules)). A unit with an empty body — whitespace and comments only — is a parse_error. A **docstring** is a string literal alone on the first line of a module, a class body (§13.1) or a `def` body (§4.2). It documents what it opens and is not a statement: nothing runs it, and a unit compiles exactly as it would with a comment in its place. A `def` whose body is only a docstring does nothing, as a body of `pass` does, and a module that holds only a docstring is still empty. A string standing alone anywhere else is an expression statement (§4.3). Except where an example shows a complete compile unit, program examples elsewhere in this document are fragments: a fragment omits the surrounding program scaffolding for brevity. ### 4.2 Declarations ``` decl := 'def' IDENT '(' params? ')' ( '->' return-type )? ':' NEWLINE INDENT ( docstring stmt* | stmt+ ) DEDENT return-type := type | 'value' | 'None' # `value`: a store fold's result, narrowed by the caller (§4.2); `None`: what no annotation says params := param-item ( ',' param-item )* # which lists are read: §4.2 param-item := param | var-param | '/' # what stands before it takes no keyword (§4.2) | '*' # what follows it takes no position (§4.2) param := IDENT ':' param-type ( '=' const-expr )? # a default value var-param := '*' IDENT ':' binding-type # variadic: surplus positionals → tuple const-expr := expr # a literal, or an operation over literals param-type := binding-type | module-name '.' IDENT # a class an imported module declares (§13.2) binding-type := type type := type-name | IDENT # a class name (§13.1), in EVERY type position (§5.5) | 'list' '<' type '>' | 'set' '<' type '>' | 'dict' '<' type ',' type '>' | 'tuple' '<' type ( ',' type )* '>' | 'iterator' '<' type '>' # §8.6 | 'dict_keys' '<' type '>' | 'dict_values' '<' type '>' # dict views (§5.1) | 'dict_items' '<' type ',' type '>' | collection-word '[' type ( ',' type )* ( ',' '...' )? ']' # any of them in CPython's brackets, the same type (§5.5) type-name := scalar-name | 'active_claim' | 'claim_list' | 'proof_tree' | 'diagnosis' # read-result types (§5.1) | 'subject_set' | 'string_list' | 'verify_result' | 'result_type_set' | 'slice' # a slice value (§5.3) scalar-name := 'string' | 'int' | 'bool' # primitives | 'datetime' | 'duration' | 'real' | 'blob' | 'scientific' # value-class scalars (§5.5) | 'complex' | 'str' | 'float' # CPython's names for `string` and `real` (§5.5) collection-word := 'list' | 'set' | 'dict' | 'tuple' | 'iterator' | 'dict_keys' | 'dict_values' | 'dict_items' ``` A declaration is the `def` keyword, a name, a parenthesised parameter list, an optional **`-> type`** return type, a `:`, and an indented suite. A parameter is `name: type` where `type` is one of the type names enumerated in §5.5 — a primitive (`string`, `int`, `bool`), a value-class scalar (`datetime`, `duration`, `real`, `blob`, `scientific`, `complex`), or a read-result type such as `claim_list` — **or** a parameterized collection type: `list`, `set`, `dict`, or `tuple`, nestable (e.g. `dict>`). So a collection crosses a function boundary both as an argument and as a return value. A class crosses it both ways, as a collection does. A parameter may name a class and receives an instance of it, and a return type may name one too (§13.2). (`empty` and `null` name absence and are never parameter types.) The declared objects are called *scripts* (the wire addresses them as such, see [MCP Reference §9.2](/ref/dke/mcp/#scripts-address-by-module.script)). **One name space.** A compile unit declares four kinds of named thing — a script, a standing rule (§14.1), a standing constraint (§14.10) and a class (§13.1) — and they share one name space: no two declarations in a unit may have the same name, whatever their kinds. A second declaration of a name the unit already holds is a type_error (§10.2), reported at that declaration's name, and its diagnostic names the kind of the declaration holding the name. Python also binds a class and a function in one module name space, and rebinds a name declared twice; DKE Python refuses the second declaration instead. **Void scripts and value scripts.** A script with no `-> type` whose body holds no `return ` is a **void script**: it produces no value and is called for its store effects and `print` output. A script written `def name(...) -> type:` is a **value script**: it produces a value of the declared `type`, and every path through its body MUST end in a `return ` of that type (the exhaustive-return rule, §6.9). A value script may be called in expression position, its result bound or composed (§4.4, §6.8); a void script may not (using a void script's call as a value is a type_error). The return `type` is any of the parameter types above, including a collection type (`-> list`, `-> dict`, …); an empty `return []`, `return set([])` or `return {}` satisfies any concrete `-> list`, `-> set` or `-> dict`. A script with no `-> type` whose body holds a `return ` is a value script too, of the type its `return`s give, and behaves as one written with that type, wherever its call stands. The wider of two types one of which fills the other is that type, so a `return` of an `int` and one of a `real` give `real`. Two `return`s of types neither of which fills the other are a type_error, and so is a path that ends with no value, and a `return` no type is known for, such as an element of a list bound empty, which is told so, annotated or not, and the annotation it takes is on that name. So is a body whose type waits on the type of a body that may call it back: writing `-> ` on either settles what is no cycle (§9.7). The `return` statement itself is specified in §4.3 and §9.2.8. **`-> value`.** A value script may also declare `-> value`, as a value method may (§13.4): Python has one construct for both. It is the return type of a script that hands on a **store fold's** result, such as `m = max(K.attr)`, whose class is whatever the cells hold. The caller narrows it with `match` (§6.2), and so does a script that would rather promise a narrower type: `-> int`, then `match m:` and `case int as n:` and `return n`. Only an expression whose type is `value` fills a `-> value` return, so `return 1` is a type_error (§6.4), as it is in a method. A parameter may carry a **default value** (`def f(x: int, y: int = 1)`). The default is a **constant expression** — a literal, or an operation over literals (so `-1`, `1 + 2`, `[1, 2]` qualify) — and its type must match the parameter's declared type or fill it (§6.4): a `bool` default is accepted for an `int` parameter, and either for a `real` one. A parameter with a default may not precede one without (a required parameter after a defaulted one is a compile-time error). A call that omits a defaulted argument uses the default; a required parameter with no supplied argument is an error (§4.3, §9.7). **Variadic parameter.** A parameter may be written `*name: T` — a **variadic** parameter that collects the surplus positional arguments into a `tuple` (a tuple whose every element is `T`), possibly empty. There may be at most one, its element type `T` is required, and it takes no default. A call passes any number of trailing positional arguments of type `T` for it (`f(a, 1, 2, 3)`); it cannot be supplied by keyword. Inside the body the parameter is an ordinary `tuple` — indexed (`name[0]`), measured (`len(name)`), iterated (`for x in name`), and reduced (`sum`/`max`/comprehension). There is no `**kwargs`: a signature names every keyword a call may pass, which is what lets a misspelt keyword be refused where it is written rather than collected as data. Use explicit named parameters — with defaults where useful — or a `dict` parameter. **Positional-only and keyword-only parameters.** A `/` makes the parameters before it **positional-only**, and a bare `*` or a variadic parameter makes the parameters 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)`. A keyword naming a positional-only parameter is told `` f() got some positional-only arguments passed as keyword arguments: 'a' ``, and a positional argument that would reach a keyword-only one `` f() takes 2 positional arguments but 3 were given ``. A keyword-only parameter takes a default as any parameter does, and one with no default may follow one with a default. A spread (§8.15) fills only what a position may reach. Each marker stands at most once and in that order, as §10.1 says of a list Python would not read. The listing of scripts writes each parameter list as it was written, markers included. **`--help`** is the teaching option verb (§4.1) — the in-band way to learn the language from inside it. Bare `--help` yields the orientation lesson: the declaration shape, one complete example, and the topic index. `--help ` yields one topic's lesson; the topics are `header`, `declarations`, `statements`, `expressions`, `types`, `verbs`, and `scripts`. The lesson language follows the `language` parameter (§3.9). An unknown topic is a parse_error whose diagnostic names the topic index. ``` --help types ``` A unit whose body is a help form never compiles and stores nothing; on the wire it is classified parse_error, and its diagnostic message is the lesson. A conforming implementation whose surface faces a human directly (such as a CLI) SHOULD present the lesson without error framing and report success — asking for help is not a mistake. ### 4.3 Statements A statement is either a **simple statement**, ending with a `NEWLINE` or a `;`, or a **block statement** (`if` / `for` / `match` / `try` / `branch` / `world`), whose body is a `:`-headed indented suite. Where this grammar writes that suite, the simple statements of one line may stand after the header's `:` instead, as in Python: `if a: b = 1; c = 2`, `def f(): pass`, `class C: x: int`. A block statement does not stand there, nor after a `;`. ``` stmt := simple-stmt ( ';' simple-stmt )* ';'? NEWLINE | block-stmt simple-stmt := assign-stmt | print-stmt | call-stmt | verb-call-stmt | commit-stmt | rollback-stmt | import-stmt | return-stmt # §9.2.8 | raise-stmt | assert-stmt | rebind-stmt # changes a collection, §8.14 | del-stmt # §8.14 | 'pass' # a no-op | '...' # a no-op, as `pass` is | expr-list # an expression statement: it runs, and nothing keeps its value | 'break' # loop control, §9.2.7 | 'continue' block-stmt := match-stmt | class-match # dispatch by class, §13.5 | for-stmt | if-stmt | try-stmt | branch-block | world-block # §9.2.9 assign-stmt := IDENT '=' expr-list | IDENT ':' binding-type # an annotation with no value; it binds nothing | IDENT ':' binding-type '=' expr-list # annotated assignment; the type must agree with the initializer, and only a name takes one (§6.4) | chain-target ( '=' chain-target )+ '=' expr-list # chained assignment; every target binds one value, left to right (§8.12) | IDENT aug-op expr-list # augmented assignment on a bare name; `x op= e` is `x op e`, changed in place on a list or set (a field names a source, so it uses `remember(...)`, not `op=`) | top-target ( ',' top-target )+ '=' expr-list # unpacking a tuple or list; nested targets + one optional `*rest` (§8.11) | target-group '=' expr-list # a parenthesised or bracketed target list (§8.11) expr-list := display-item ( ',' display-item )* ','? # a bare comma-list; holding a comma, it is a tuple (§3.5.11); a `*` item alone is none (§8.15) top-target := unpack-target | '*' unpack-target # a target, or THE single starred target (≤ one per list), which may be any target unpack-target := IDENT | element-target | target-group # a name, an element or a slice, or a nested target list, which may hold its own star target-group := '(' top-target ( ',' top-target )* ','? ')' # in parentheses; one target alone in them is that target | '[' top-target ( ',' top-target )* ','? ']' # in brackets; `[a]` is a list of one target (§8.11) | '(' ')' | '[' ']' # a list of no targets; the value must hold nothing (§8.11) chain-target := top-target ( ',' top-target )* ','? # a target or a target list, as an assignment takes one (§8.12) element-target := write-target subscript # an element or a slice, written as an element write writes it (§8.14) | postfix-expr subscript # an element of what an expression answers, which no name holds (§8.14) aug-op := '+=' | '-=' | '*=' | '/=' | '%=' | '//=' | '**=' | '|=' | '&=' | '^=' | '@=' rebind-stmt := write-target '.' rebind-method '(' arg-list? ')' # changes the collection the target reaches; a statement, never an expression (§8.14) | write-target '.' answer-method '(' arg-list? ')' # changes the collection and discards what the call answers; the call is an expression too (§8.14) | postfix-expr '.' ( rebind-method | answer-method ) '(' arg-list? ')' # the same, on what an expression answers, which no name holds (§8.14) | element-target '=' expr-list # an element or slice write; changes the collection the target reaches (§8.14) | element-target aug-op expr-list # `c[k] op= e` is `c[k] = c[k] op e`, the target and `k` evaluated once write-target := IDENT ( subscript | '.' IDENT '(' arg-list? ')' )* # a bound name, then any chain of subscripts and method calls; what it reaches is the collection changed (§8.14) subscript := '[' expr ( ',' expr )* ','? ']' # an index or a key; a comma makes it a tuple, `d[1, 2]` is `d[(1, 2)]` (§4.4) | '[' expr? ':' expr? ( ':' expr? )? ']' # a slice; only a list's is written (§8.14) rebind-method := 'append' | 'extend' | 'insert' | 'remove' | 'sort' | 'reverse' | 'clear' | 'add' | 'discard' | 'update' answer-method := 'pop' | 'setdefault' | 'popitem' # changes the collection and answers a value (§8.14) del-stmt := 'del' write-target subscript # removes one element, entry or slice from the collection the target reaches (§8.14) | 'del' postfix-expr subscript # an element of what an expression answers, which no name holds (§8.14) | 'del' '(' element-target ')' # in parentheses, that element (§8.14) print-stmt := 'print' '(' ( print-arg ( ',' print-arg )* )? ')' print-arg := expr # a value to render | '*' expr # each element of a collection, as an argument of its own (§8.15) | ( 'sep' | 'end' | 'flush' ) '=' expr # a keyword, read after the arguments | 'file' '=' 'None' # the default, which is the line itself | '**' dict-literal # its keys as keywords, each key a literal (§8.15) raise-stmt := 'raise' ( ( EXC-NAME ( '(' ( expr ( ',' expr )* ','? )? ')' )? | IDENT ) ( 'from' expr )? )? # a catchable refuse; any other value after `raise` is read as a raise and is a parse_error at the value (§4.3) assert-stmt := 'assert' expr ( ',' expr )? # Python assertion (the absence-assertion verb, §7.1, is separate) call-stmt := IDENT '(' arg-list? ')' verb-call-stmt := IDENT '(' arg-list? ')' # a built-in verb call (§7); the head IDENT is a verb head-word commit-stmt := 'commit' rollback-stmt := 'rollback' import-stmt := 'import' import-item ( ',' import-item )* # load each module, Python Reference §9.3; `math` and `cmath` name the built-in namespaces, §8.16, `vector` one more, §8.17, and `struct` one more, §8.18 | 'from' IDENT 'import' import-names # bind names a module declares | 'from' IDENT 'import' '(' import-names ','? ')' import-names := import-name ( ',' import-name )* import-name := IDENT ( 'as' IDENT )? # bind the name, or bind it as the name after `as` import-item := module-name ( 'as' IDENT )? # bind the module, or bind it as the name after `as` module-name := IDENT ( '.' IDENT )* return-stmt := 'return' expr-list? # early exit / value, §9.2.8 match-stmt := 'match' expr-list ':' NEWLINE INDENT case-clause+ DEDENT # two or more items, or one and a comma, are the tuple written without its parentheses (§3.5.11), as CPython reads a subject case-clause := 'case' class-label ( 'if' expr )? ':' arm-body # §6.2 | 'case' elem-pattern ( 'if' expr )? ':' arm-body # structural: an alternative is a `pattern`, §6.2 | 'case' value-pattern ( 'if' expr )? ':' arm-body # value patterns, §6.2 | 'case' 'default' ':' arm-body # never guarded, §6.2 class-label := IDENT ( '|' IDENT )* # alternatives, §6.2 | IDENT 'as' IDENT # `as` binds ONE class, never across `|` value-pattern := value-alt ( '|' value-alt )* ( 'as' IDENT )? value-alt := case-literal | '_' | IDENT | class-pattern # IDENT: a capture, a name that is no # class label, and alone; `_` last class-pattern := IDENT ( '.' IDENT )? '(' ( elem-pattern ( ',' elem-pattern )* ','? )? ')' # a class tested, §6.2 case-literal := '-'? ( INT | REAL ) | STRING+ | 'True' | 'False' | '-'? IMAG | '-'? ( INT | REAL ) ( '+' | '-' ) IMAG # a complex, §3.5.15 arm-body := simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT pattern := seq-pattern | map-pattern # a SHAPE label, §6.2 seq-pattern := '[' ( elem-pattern ( ',' elem-pattern )* ( ',' '*' IDENT )? | '*' IDENT )? ']' # a `*` MUST be last map-pattern := '{' ( case-literal ':' elem-pattern ( ',' case-literal ':' elem-pattern )* )? '}' # no key named twice, §6.2 elem-pattern := elem-alt ( '|' elem-alt )* ( 'as' IDENT )? # the same names bound by # each; only the last may match every # value; IDENT is not `_` elem-alt := IDENT | '_' | case-literal | pattern | class-pattern # one per §6.2 element row, plus # `'*' IDENT` above, whose position # is constrained, and the `|` and the # `as` above for-stmt := 'for' top-target ( ',' top-target )* ','? 'in' expr ':' NEWLINE INDENT stmt+ DEDENT # a target list unpacks each element — the SAME list an assignment takes (§8.11) # (an element target among them is written each iteration; `top-target` is defined with `assign-stmt` above) if-stmt := 'if' expr ':' NEWLINE INDENT stmt+ DEDENT ( 'elif' expr ':' NEWLINE INDENT stmt+ DEDENT )* ( 'else' ':' NEWLINE INDENT stmt+ DEDENT )? try-stmt := 'try' ':' NEWLINE INDENT stmt+ DEDENT handler* handler := 'except' 'refuse' ( 'as' IDENT )? ':' NEWLINE INDENT stmt+ DEDENT | 'except' 'engine_error' ( 'as' IDENT )? ':' NEWLINE INDENT stmt+ DEDENT branch-block := 'with' 'branch' '(' STRING ')' ':' NEWLINE INDENT stmt+ DEDENT world-block := 'with' 'world' '(' expr ')' ':' NEWLINE INDENT stmt+ DEDENT # `world` is a soft keyword here arg-list := arg ( ',' arg )* arg := expr # positional | '*' expr # a spread: each element a positional argument of its own (§8.15) | IDENT '=' expr # a keyword argument ``` ``` # several statements on a line, and a body on its header's line def tally(a: int, b: int): c = a + b; d = a - b; if c > d: print(c); print(d) ``` In a call's `arg-list`, a **positional** argument fills the next unfilled parameter left-to-right; a **keyword** argument `name=value` fills the parameter of that name. A positional argument may not follow a keyword argument or a `**` argument, and a `*` argument may not follow a `**` argument. Each is a grammar error, as it is in Python, and is answered where Python answers it (§10.1). So is a `*` that no expression follows, written after another argument. A parameter filled by neither uses its default (§4.2); binding the same parameter both positionally and by keyword, naming a parameter that does not exist, supplying more positional arguments than parameters, or leaving a required parameter unbound are each errors, told in Python's words and in its order: each keyword as written, a keyword no parameter takes, too many positional arguments, a positional parameter left unbound, then a keyword-only one. A method's count holds its receiver, and it is named by the class whose body holds its `def`. A positional argument written `*xs` is a **spread**: each element of `xs` is a positional argument of its own, in its place. Where a spread's length is known only as the program runs, or the call has a `**` argument, or no type is known for what the statement reads, those errors are found as it runs (§8.15), and a line that does not run is told nothing. A **bare assignment** (`assign-stmt`) binds one name to an expression result; the right-hand side MUST NOT be a statement-only verb (§7.4). A **call statement** whose head identifier is not a verb head-word is a stored-script invocation — how a program calls compiled code, including from inside a `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). Resolution follows the compile-before-call rule (§6.8, §9.7). A call statement may name a void OR a value script; when it names a value script the returned value is discarded (§9.2.8). A builtin there, where no script of that name resolves, is an expression alone: it is evaluated and its value dropped, so `len(xs)` runs and `next(e)` reads an element and discards it, since reading changes the iterator. An **augmented assignment** (`IDENT aug-op expr`) computes the plain form: `x op= e` is `x op e`, with `x` evaluated once, and binds it to `x` — except on a list or a set, below. It applies to a bare name only — a field names a source, so a field is written with `remember(...)` rather than an augmented operator. Because a variable keeps one type across a function (§6.9), the rewritten right-hand side must still have the variable's type: `/` is true division and yields a `real` (§8.3), so `/=` applies to a `real` target and `x /= 2` on an `int` is a type_error naming the type change, not a silent truncation. On a list, `+=` and `*=` change the list the name holds in place to `x + e` or `x * e` (§8.4), and on a set `|=`, `&=`, `-=` and `^=` change the set in place (§8.6), as Python's do — so every other name holding that list or set sees the change (§8.14). On a number, a string or a tuple, which do not change, the name is bound to the new value and another name keeps the old one, as in Python. ``` # augmented-assign.dpy — `x op= e` is `x = x op e`, and `/=` needs a real target. def augmented(start: int) -> int: total = start total += 10 total -= 3 total *= 2 total //= 4 total %= 7 total **= 2 rate = 1.0 rate /= 8 print("total=" + str(total) + " rate=" + str(rate)) return total ``` Invoked with `5`, that prints `total=36 rate=0.125`. An augmented operator after what Python does not augment — anything but a name, an attribute or a subscript, in any parentheses around it whole — is a parse_error where that target begins, as in Python, naming what it is: `f() += 1` is a parse_error at column 1, "'function call' is an illegal expression for augmented assignment", and `(a, b) += 1` one naming a 'tuple'. A comma, a `:` or a `**` in a `lambda`'s header is no part of what its bracket holds, so `(lambda x, y: k) += 1` names the 'lambda', at column 2, and `{lambda: 1} += 1` a 'set display'. Python reads the value after the operator before it answers, so a grammar error there is answered first: `f() += a if b` is a parse_error at column 8, a conditional with no `else`. The value need only begin, as `1 +` does; where none begins, as in `f() +=`, the answer is at the operator, at column 5. A `lambda` begins one only where Python reads its header, so `f() += lambda x=1 y: 1` is answered at the operator too. `True`, `False` and `None` are refused as the reserved words they are. A **`return` statement** (`return-stmt`) ends the enclosing script. A bare `return` (no expression) exits early; it is valid in any script, void or value. Outside a script `return` is refused, in Python's words (§10.1). A `return ` yields the expression's value and is valid only in a **value script** (§4.2): the expression's type MUST match the declared return type or fill it (§6.4) — a `bool` satisfies a `-> int` return and either satisfies a `-> real` one. A value script that does not `return` on every path is a type_error (§6.9). The return control-flow semantics are specified in §9.2.8. The subject of a **`match` statement** (`match-stmt`) is an `expr-list`: two or more items, or one item followed by a comma, are the tuple written without its parentheses (§3.5.11), so `match d, e:` is `match (d, e):` and a `*` item spreads into the tuple as it does in a parenthesized one. A `*` item alone is no tuple and no value, and is a parse_error at the `:` after it, as it is in CPython. Each **case arm** of a `match` may be an inline statement or an indented suite; over a type with a case set the arms must be exhaustive (§6.2). A case label is one discriminator, or two or more separated by `|` — an **alternation**, matching if the scrutinee's discriminator is any one of them (§6.2) — and may be followed by `if `, a **guard** the arm must also satisfy to be taken (§6.2). The optional `as IDENT` on a case label binds the matched content and is well-typed only on a value-match arm (§5.6, §6.2), and only where that arm names a single class; elsewhere it is a type_error. The `try` handler clauses are optional and may appear in **any order** and **any number**, and where more than one handles the same category the **first** of them runs (§10). The `as ` binding on a handler is itself optional — `except refuse:` fires the handler without naming the caught exception (Python's `except ValueError:`); omit it when the handler body does not use the refuse/engine_error info. `commit` and `rollback` are bare statements resolving to the enclosing `branch` block (§9.2.6). An **`import` statement** (`import `) loads a stored module by name ([Python Reference §9.3](/ref/dke/python/#import)); `import` is a **contextual keyword**, recognised only as the statement head immediately before a module name (like the aggregate fold names, §7.1.1), and an ordinary identifier elsewhere. An `import` naming a module the store does not hold fails when control reaches it, with `no module named `. A program that uses such a module, or has a `type_error` below a top-level `import` of it, is refused when it is compiled, at that `import`, in the same words — as CPython stops at the import before reaching either ([Python Reference §9.3](/ref/dke/python/#import)). `from import , …` loads the module as `import ` does and binds each named script or class, so a later `area(2, 3)` means `geo.area(2, 3)` and compiles to the same program. `from` is contextual too. A name the module does not declare is refused at the import, used or not, as CPython refuses it. Like `import`, it runs the module's body each time control reaches it ([Python Reference §9.3](/ref/dke/python/#import)), where CPython runs a module once. Only the names are bound: `.` needs an `import ` of its own, as in CPython. 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; Python would rebind it. Naming it in an annotation, as in `item: Site = b`, uses it and binds nothing. A diagnostic names a bound script or class as the unit binds it, `Site` rather than `geo.Site`, so a form it offers compiles as written. `import a, b` loads each module in turn. `as` binds another name, so after `import geo as g` a use of `g` means `geo`, and after `from geo import area as ar` a use of `ar` means `geo.area`, each exactly as if the module's name had been written; `geo` itself is not bound by either, as in Python. A name an import bound is refused if it is bound again to something else, since every later use already reads as the first. A wildcard (`from m import *`), a dotted module and a relative import are refused, each naming the form to write. `from __future__ import , …` loads nothing and binds nothing. Python's features are all accepted and change nothing here. It stands where Python requires it, at the top of a unit, with only a docstring and other such imports above it. Anywhere else, and for a feature Python does not know, it is refused in Python's words (§10.1). A **`print` statement** (`print-stmt`) writes one output line. It renders each positional argument to its text form and joins them with `sep` (default a single space `" "`), then appends `end` (default a newline) — so `print(a, b)` writes one line ` ` and `print()` writes an empty line. A positional argument written with `*` spreads a collection: each of its elements is an argument of its own, so `print(*xs)` writes the elements of `xs` joined by `sep` (§8.15). `sep` and `end` are optional keyword arguments, each any expression that gives a string, and are read after the positional arguments, in the order written. `None` written as the whole of either is its default. One that gives anything else is a catchable refuse (§10) when the line is composed, "sep must be None or a string, not int". `flush` is read in its turn, may hold anything, and changes nothing. `file` may be given `None` alone, which is its default, the line itself; given anything else it is a parse_error. A keyword `print` does not take is a type_error in Python's words, the first one written, `'x' is an invalid keyword argument for print()`, followed by the keywords it takes. A `**` before a dict display gives these keywords as its keys (§8.15): `print("a", "b", **{"sep": "-"})` is `print("a", "b", sep="-")`. A `**` before any other dict, such as a name that holds one, gives them as the line is composed, and a name written beside it that `print` does not take is refused then, once its keys are merged (§8.15). Because output is line-oriented (each `print` writes exactly one line, as `print` does), a trailing newline in `end` is the line itself; `end=""` therefore reads the same as the default (there is no line continuation). `print` renders each value exactly as `print` and the `str()` conversion do — in particular a boolean renders as `True` / `False`. `print` is a **contextual keyword**, recognised only as a statement head immediately before `(`, and an ordinary identifier elsewhere. A **`pass` statement** is a no-op: it has no effect and produces no output. It exists so a block that would otherwise be empty stays well-formed (e.g. an `else` branch that does nothing). `pass` is reserved (§3.7), as it is in Python: it is a statement and names nothing, so `x = pass` is a parse_error at column 5. A **`raise` statement** (`raise-stmt`) raises a **recoverable error** — a `refuse` (§10) — that an enclosing `try` catches with `except refuse as r:`. It names one of the built-in exception names (`ValueError`, `KeyError`, `TypeError`, `RuntimeError`, and the like); that name is recorded on the caught handler's `r.discriminator`, and its argument (rendered to its text form) on `r.reason`. Several arguments are held as CPython holds them, and the reason is the text of their tuple: `raise ValueError("a", 1)` gives `('a', 1)`. A bare `raise ValueError` gives an empty reason. Every exception name behaves identically — there are **no custom exception types and no exception hierarchies**. A `raise` never falls through, so a branch that ends in one counts as returning for the exhaustive-return rule of a value script (§4.2). `raise` is reserved (§3.7), as it is in Python: a statement head before any value. `raise` **alone** raises again the exception being handled: in an `except` body, in a `finally` body while one is pending, and in a script either of them calls. It keeps its category, its `r.reason` and its `r.discriminator`. With nothing being handled it is a `RuntimeError` whose reason is `No active exception to reraise`, as the program runs, as in Python. A handler that ends restores the one around it. `raise e` raises what the name `e` holds, which is an exception an `except` caught: the name it bound, or another name bound to that. A name holding anything else, or nothing, is a type_error teaching the exception names at the name. `from` gives a **cause**, which is `None` or an exception an `except` caught. Any other value is a type_error in Python's words, `exception causes must derive from BaseException`. Nothing here reads a cause back, so a cause changes nothing a program can observe; it is evaluated after the message, and one that fails fails in the raise's place. An exception name is no value in this language (§10), so a cause written `ValueError("a")` is the same refusal it is anywhere else. A value that is not a built-in exception name or a name alone, as `raise 1` or `raise (e)`, is a parse_error at the value, teaching the exception names, and a grammar error inside or after the value is answered where CPython answers it, as `raise not c d` is at column 13. It names nothing, so where it is assigned, read or written after a `.`, the parse_error is where Python answers it: `raise = 1` is one at column 7, and `x.raise = 1` one at column 3. An **`assert` statement** (`assert-stmt`) is Python's assertion. `assert cond` evaluates `cond` (which MUST be a `bool`) and, when it is false, raises an `AssertionError` — equivalent to `if not cond: raise AssertionError`, a recoverable `refuse` (§10) an enclosing `try` catches with `except refuse as r:`. The optional second operand is the message: `assert cond, msg` records `msg` (rendered to its text form) on `r.reason`, exactly as `raise AssertionError(msg)` would, and is evaluated **only when the assertion fails**; a bare `assert cond` gives an empty reason. A passing assertion produces no output and execution continues. This is the statement form of `assert`, and a condition may begin with a bracket: `assert (x)`, `assert (x), "m"` and `assert (x) == 1` are all this statement. The same word heads the absence-assertion **verb** (§7.1) in its call form `assert(path, source)`, which records an explicit-absent claim. The verb is read where the bracket ends the statement and holds a first argument ending in a dotted name, and then a comma, and where it ends the statement holding one dotted path whose first name the program binds nowhere. So `assert (x.y, "m")`, which Python reads as a tuple and never fails, is the verb here. A **rebinding statement** (`rebind-stmt`) and a **`del` statement** (`del-stmt`) change the list, set or dict a name holds, or one it reaches through subscripts and method calls, in place, so every name holding it sees the change: `xs.append(v)`, `d[k] = v`, `m[i][j] = v`, `xs[i:j] = ys`, `del d[k]`. Each answers nothing and is a statement of its own, never an expression. The exceptions are `pop`, `setdefault` and `popitem` (`answer-method`), which change the collection and answer a value: a call of one is an expression, and as a statement its answer is discarded. Their semantics, typing and refusals are §8.14. `del` is a reserved word (§3.7), and heads a statement immediately before a name, or before the element or slice of a value no name holds that it removes, as in `del (xs)[0]` or `del [xs][0][0]` (§8.14). Parentheses around a name or an element at the head of a statement are that name or element, as in Python: `(xs).append(v)`, `(c).hi()` and `(f)()` are the statements without them. ### 4.4 Expressions ``` expr := cond-expr cond-expr := or-expr ( 'if' or-expr 'else' expr )? # ternary; right-assoc; lower than `or` or-expr := and-expr ( 'or' and-expr )* and-expr := cmp-expr ( 'and' cmp-expr )* cmp-expr := bitor-expr ( cmp-op bitor-expr )* cmp-op := '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in' | 'is' | 'is' 'not' bitor-expr := bitxor-expr ( '|' bitxor-expr )* # set union, §8.6 bitxor-expr := bitand-expr ( '^' bitand-expr )* # set symmetric difference bitand-expr := add-expr ( '&' add-expr )* # set intersection add-expr := mul-expr ( ( '+' | '-' ) mul-expr )* mul-expr := unary-expr ( ( '*' | '/' | '//' | '%' | '@' ) unary-expr )* unary-expr := ( 'not' | '-' | '+' ) unary-expr | pow-expr pow-expr := postfix-expr ( '**' unary-expr )? # right-assoc; tighter than unary `-` postfix-expr := primary-expr postfix* postfix := '.' IDENT '(' arg-list? ')' # method call (§13.4; string methods §5.3) | '.' IDENT # field access | '[' display-item ( ',' display-item )* ','? ']' # indexing (string / collection, §5.3); a comma or a `*` makes the index a tuple (§8.15) | '[' expr? ':' expr? ( ':' expr? )? ']' # slice (string / list / tuple, §5.3) primary-expr := STRING | INT | 'True' | 'False' | fstring-literal # formatted string literal (§3.5.10) | list-display # list literal / comprehension (§3.5.9, §8.9) | brace-display # dict/set literal or comprehension (§3.5.12, §3.5.13, §8.9) | tuple-display # tuple literal / grouping (§3.5.11) | builtin-call # zip / enumerate (§8.6), abs / round (§8.3) | namespace-member # math / cmath (§8.16), vector (§8.17), struct (§8.18) | IDENT '(' expr ')' # instance constructor (§13.2) | module-name '.' IDENT '(' expr ')' # module-qualified construction (Python Reference §9.6) | value-call # value-script invocation (§6.8) | hypothesis # suppose (§15) | absence-hypothesis # suppose_absent (§15.1) | IDENT | path-expr | IDENT '(' arg-list? ')' # a built-in read verb call, §7 value-call := IDENT '(' arg-list? ')' # a value script's call, in expression position list-display := '[' ( display-item ( ',' display-item )* ','? )? ']' # list literal (§3.5.9); a spread, §8.15 | '[' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ ']' # list comprehension (§8.9) brace-display := '{' ( dict-item ( ',' dict-item )* ','? )? '}' # dict literal (§3.5.12) | '{' display-item ( ',' display-item )* ','? '}' # set literal (§3.5.13) | '{' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ '}' # set comprehension (§8.9) | '{' expr ':' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ '}' # dict comprehension (§8.9) tuple-display := '(' expr ')' # grouping (no comma) | '(' ( display-item ( ',' display-item )* ','? )? ')' # tuple literal (§3.5.11); a `*` item alone needs its comma builtin-call := ( 'zip' | 'enumerate' | 'reversed' | 'range' | 'abs' | 'round' | 'divmod' | 'str' | 'int' | 'float' | 'list' | 'set' | 'dict' | 'isinstance' | 'issubclass' ) '(' ( ns-arg ( ',' ns-arg )* )? ')' # zip/enumerate/reversed/range §8.6, abs/round/divmod §8.3, conversions §8.13, isinstance §13.9, issubclass §13.10; a spread §8.15 namespace-member := ( 'math' | 'cmath' | 'vector' | 'struct' ) '.' IDENT ( '(' ( ns-arg ( ',' ns-arg )* ( ',' IDENT '=' expr )* )? ')' )? # §8.16–§8.18; a constant takes no call, a function takes one ns-arg := expr | '*' expr # a value, or a spread of its elements (§8.15) path-expr := IDENT ( '.' IDENT )+ # K.s.a slot-grammar dotted path ``` Prefix `+` takes what prefix `-` takes and changes no number, and any other operand is a type_error; a `bool` gives the `int` it is, so `+True` is `1`. Prefix `-` (unary minus) negates an `int`, a `real` or a `duration`, and any other operand is a type_error. Negating an `int` has the runtime semantics of `0 - a` (§8.3), so negating the most-negative integer overflows and refuses rather than wrapping; negating a `real` keeps the sign of zero, so `-a` is `-0.0` when `a` is `0.0`, as in Python. The boundary between `path-expr` (a multi-segment dotted reference to a store cell) and `postfix-expr` with `.field` chains (field access on a value) is resolved by the typechecker (§6) using the type of the head identifier. The comparison operators **chain** (§4.6): `a < b < c` is `(a < b) and (b < c)` — each adjacent pair compared, the results conjoined, with `b` evaluated only once. A **value-call** `f(args)` is a value script's invocation (§4.2) used as an expression: its value is what the callee `return`s, and its type is the callee's declared return type (§6.8). It composes like any expression — assigned (`y = f(x)`), returned (`return f(x)`), passed as an argument (`print(f(x))`, `g(f(x))`), or combined with operators (`f(a) + g(b)`); nested value-calls evaluate inner-first, left-to-right (§9.1). The head identifier disambiguates `IDENT(...)` between the three call-shaped primaries: a class name is an instance constructor (§13.2), a built-in name the program has not bound is that built-in (`zip`/`enumerate`/`len`/ `abs`/`str`/`int`/`float`/`list`/`set`/`dict`/a fold/a verb), and any other identifier bound to a value script is a value-call. A name the program has bound comes first, as Python's lookup finds it first. A `def` of the unit is what a call of its name finds in any body and in any statement below it, whatever built-in shares the name, `print` included, while a statement above it still reaches the built-in. A name holding a value is not called, whatever else carries the name: `max = 3; max(1, 2)` is a type_error at the call, `'int' object is not callable`, as `x = 3; x(1)` is and as `get = 3; get(1)` is, and Python raises that `TypeError` as the program runs. A body that names a built-in which the statements above its `def` have bound to a value is refused too, since no body reads a name from outside it (§4.2), and so is one that names a built-in the statements below it bind. A class holds its name ahead of a built-in of that name in every body of the unit, and from its own line at the top of the unit, so a statement above it still reaches the built-in. A method's name is its class's and takes nothing from the built-in. A name a body binds anywhere is that body's from its first line. An assignment binds one, and so do a `for`, a capture in a `case`, an import and an `as`. A call or a read of it above the binding is a type_error, `cannot access local variable 'max' where it is not associated with a value`, where Python raises that `UnboundLocalError` as the program runs. A comprehension's variable takes the name through the comprehension but for its first source, which is read outside it, and holds it from there on: a later source or an `if` that reads a name a later clause binds is that type_error too, whatever an outer scope holds under the name. Read from inside a comprehension, a body's name or an outer comprehension's is a free variable, and the words are `cannot access free variable 'q' where it is not associated with a value in enclosing scope`, where Python raises that `NameError`. A module is no callable: a call of one is a type_error, `'module' object is not callable`, where Python raises that `TypeError` as the program runs. An `except`'s name holds through its handler, and at the top of a unit the built-in is found again past it, since Python deletes the name there. `__debug__` reads as `True`, as it does in Python, and nothing may bind it or delete it. A call to a **void** script (§4.2) in expression position is a type_error; call it as a statement (§4.3) instead. A **subscript** that holds a comma indexes by a tuple, as Python reads it: `d[1, 2]` is `d[(1, 2)]`, and `d[1,]` is `d[(1,)]`. That holds for a write and a `del` through one too (§8.14). A slice beside another index, as in `xs[1:2, 0]`, is a parse_error at the slice's `:`, since no value is indexed by a tuple that holds a slice. Every part of the subscript is read first, so a grammar error anywhere in it is the answer instead. ### 4.5 Verbs The built-in verb surface (§7) 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 produce a value and stand in expression position as the whole right side of an assignment or the whole value of a `return` (§7.1.1), not inside a larger expression; write verbs have no value and stand only as statements. Representative forms, as they read in a program: ``` remember(K.s.color, "orange", "op1") # write: (path, value, source) update(K.s.color, "black", "op1") forget(K.s.color) # path-scoped assert(K.s.nickname, "op1") # absence assertion: (path, source) cur = current(K.s.color) # reads hist = get(K.s.color) proof = why(K.s.color) attrs = list_attributes(K) # K or K.s subs = list_subjects(K.color) vals = list_values(K.color) hits = subjects(K.color, "orange") cur = current(K.s.color) # the current claim at this cell deps = dependents(cur) # claims derived from that claim prem = premises(cur) # claims that one was concluded from v = verify(K.s.color, "orange") # optional: verify(K.s.a, v, src) n = agreement(K.s.color, "orange") # how many sources recorded "orange" ``` See §7 for each verb's slot shape and result type. A path whose segment count does not match the verb's slot shape is a runtime refuse (§7.2). ### 4.6 Operator precedence and associativity The conditional expression `a if c else b` (§8.10) binds **lower** than every operator in the table below — its `then` and `else` operands are full expressions, so `x + 1 if c else y * 2` groups as `(x + 1) if c else (y * 2)`, and it is right-associative (`a if p else b if q else c` is `a if p else (b if q else c)`). Below the conditional expression, lowest to highest precedence; binary operators are left-associative **except `**`, which is right-associative** (`2 ** 3 ** 2` is `2 ** (3 ** 2)`). | Level | Operators | |-------|-----------| | 1 | `or` | | 2 | `and` | | 3 | `not` (boolean, prefix) | | 4 | `==` `!=` `<` `>` `<=` `>=` `in` `not in` `is` `is not` | | 5 | `\|` (bitwise or, set union) | | 6 | `^` (bitwise exclusive or, set symmetric difference) | | 7 | `&` (bitwise and, set intersection) | | 8 | `<<` `>>` (shifts) | | 9 | `+` `-` (binary) | | 10 | `*` `/` `//` `%` `@` | | 11 | `-` `~` (prefix, arithmetic and bitwise) | | 12 | `**` (power, right-associative) | | 13 | `.` (field / method) , `[]` (index) , instance / verb call | Boolean `not` binds **looser** than comparison (Python), so `not a == b` is `not (a == b)`; it is distinct from the arithmetic prefix `-`, which binds tightly (level 11). The bitwise operators and the shifts sit between comparison and `+` / `-`, in Python's order, so `c <= a | b` compares `c` with the union, `a | b & c` is `a | (b & c)`, `a - b | c` is `(a - b) | c`, and `1 + 2 << 3` is `24`. `**` binds more tightly than the prefix `-` and `~`, so `-2 ** 2` is `-(2 ** 2)` = `-4`; its right operand is a unary expression, so `2 ** -1` is well-formed. Comparison operators **chain** (Python semantics): `a op1 b op2 c …` is `(a op1 b) and (b op2 c) and …` — each adjacent pair is compared under §8.1 and the results conjoined with `and`. An interior operand is compared in both of its pairs; because DKE operands are side-effect-free (a store read is bound to a name first, §9.1) the result is well-defined. A single comparison (`a < b`) is unchanged. --- ## 5. Type system DKE Python has a closed vocabulary of built-in types. The one way a program adds a type is a class (§13.1): a class name is a type, and an instance of a subclass is accepted wherever its base is named (§13.8). There are no user-defined generics; the built-in parametric types are the constructed collections `list`, `tuple<…>`, `dict` and `set` (§3.5.9, §3.5.11–§3.5.13, below). Programs compose by operating on the store through verb calls. ### 5.1 The base types | Type | Origin | Role | |------|--------|------| | `string` | user data; identifier-typed slots; values | primitive | | `int` | counts (`.length`), positions, timestamps | primitive | | `bool` | comparison + logical results | primitive | | `value` | the stored content of a claim (`active_claim.value`, §5.6); result of `suppose(…)` (§15) | tagged union | | `datetime` | a UTC instant, written `datetime("…Z")` (§3.5.5); the `datetime` class of `value` (§5.6) | value class | | `duration` | a length of time, written `duration("…")` (§3.5.6); the `duration` class of `value` (§5.6) | value class | | `real` | a fractional number, written `3.5` (§3.5.7); the `real` class of `value` (§5.6) | value class | | `blob` | an opaque byte string, written `blob("…")` or `blob_b64("…")` (§3.5.8); the `blob` class of `value` (§5.6) | value class | | `scientific` | a number carrying its significant figures, written `scientific("…")` (§3.5.14); the `scientific` class of `value` (§5.6) | value class | | `complex` | a number with a real part and an imaginary part, written `3 + 4j` (§3.5.15); the `complex` class of `value` (§5.6) | value class | | `slice` | the result of `slice(stop)` or `slice(start, stop[, step])`: three bounds that select from a sequence as `xs[a:b:c]` does (§5.3) | sequence selector | | `active_claim` | result of `current(K.s.a)` and `suppose_absent(…)` (§15.1) (single live claim) | discriminator | | `claim_list` | result of `get(K.s.a)`, `list_values(K.a)`, `caveats(K.s)`, `conflicts()`, `dependents()`, `premises()` | finite collection | | `proof_tree` | result of `why(K.s.a)` | finite collection (also `.root`, `.truncated`) | | `diagnosis` | result of `why_not(K.s.a)` and `what_needs(K.s.a)` | finite collection | | `subject_set` | result of `subjects(K.a, v)` and `list_subjects(K)` / `list_subjects(K.a)` | finite collection | | `string_list` | result of `list_categories()`, `list_attributes(K)`, `list_pinned()`, `list_scripts()` | finite collection | | `verify_result` | result of `verify(K.s.a, v)` | discriminator | | `result_type_set` | result of `list_result_types()` | finite collection | | `refuse_info` | the binding of `except refuse as e` (§10) | handler binding | | `engine_error_info` | the binding of `except engine_error as e` (§10) | handler binding | | `empty` | sentinel case of an `active_claim` match | discriminator | **All six** **finite collection** types may appear as the iterand of a `for … in`, which also admits a constructed `list` / `set` / `dict`, a `string`, and a `tuple` whose elements all have one type (§6.3). Four of them (`subject_set`, `string_list`, `result_type_set`, `diagnosis`) yield `string` elements; `claim_list` and `proof_tree` yield `active_claim` elements. A `diagnosis` is the explanation of an absence: printing it shows the whole answer, and iterating it takes that answer a line at a time. It carries no claims, because the question it answers is about a claim that is not there. A `proof_tree` is the collection of claims that participated in a `why` derivation. 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`. Iterating it (or reading `.length` / `[i]`, §5.3) yields those claims, each an `active_claim` carrying the same fields a `claim_list` element does (`.value`, `.source`, `.trust`, `.created_at`); a `proof_tree` additionally offers a `.root` accessor naming the queried claim and a `.truncated` accessor saying whether the derivation shown is the whole one (§5.2). `match` is meaningful over the **discriminator** types (`active_claim` with its `empty` case, and `verify_result`) and over the `value` **tagged union**, which a `match` destructures by the class of the stored content (§5.6, §6.2). **Listing order is part of this contract.** Each of the six has a defined order. `claim_list` has two, because 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 in ascending `created_at` | | `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 | **Ascending name order** is the order `sorted()` gives (§8.6) — codepoint order, not locale collation — the same **deterministic sorted order** a constructed `set` is presented in, and, like that one, a defined refinement of Python rather than a promise Python makes. So `xs == sorted(list(xs))` holds for every row that says ascending, and sorting such a read again is dead code. This is stronger than the stability §9.6 already guarantees: it fixes not only that the order does not vary between runs but what the order **is**. The two structural rows are as binding as the sorted ones — a `proof_tree` leads with its conclusion, and a `diagnosis` reads top to bottom as written. **`result_type_set` sorts by type name**, in the ascending name order above. **`list_result_types()` has a published membership** — these nine, in this order: ``` active_claim, claim_list, diagnosis, empty, proof_tree, result_type_set, string_list, subject_set, verify_result ``` They are the result types **a program can hold**. Eight of them are also type names you may write in an annotation; `empty` is the ninth member and is not one — it is the sentinel case of an `active_claim` (below), and it is listed because a program can hold it, which is the question this verb answers. **These names are canonical.** They are the same strings `.kind` yields and the same `case` labels a `match` compares against. `empty` is never produced standalone; it is the second case of an `active_claim` discriminator. The typechecker recognises `empty` as a valid `case` label on an `active_claim` scrutinee, not as a standalone expression. `value` (§5.6) is not a user-composed type — it is the single built-in union, carrying whichever primitive a claim's content happens to be. Classes (§13) remain the only user-defined types, and a class's bases the only subtyping. **Constructed lists (`list`).** Separately from the store-derived finite collections above, a program may **build** a finite list with a list literal (§3.5.9) or a comprehension (§8.9): `[1, 2, 3]` is a `list`, `["a", "b"]` a `list`, `[[1], [2]]` a `list>`. `list` is the one **element-parametric** type — `T` is the element type, itself any expressible type including another `list`. A literal whose elements share a type is a `list`; a literal mixing scalar value classes is a `list` (holding numbers when they are all `int`s and `real`s, §5.6); the empty list is `list` until context fixes `T`. An empty `list` is compatible with any `list`: wherever two list types must agree — the two branches of a conditional expression (`[1, 2] if c else []`), a variable's re-binding (`xs = []` then `xs = [1]`), an annotated binding (`xs: list = []`), the sibling elements of a nested list literal (`[[1, 2], []]` is `list>`), a list-typed method argument (`",".join([])`), and list equality (`[] == xs`) — an empty list takes the concrete side's element type. The empty set (`set([])`, a `set`) and the empty dict (`{}`, a `dict`) are compatible the same way with any set and any dict, at the same sites: `seen = set([])` then `seen |= {"a"}` binds `seen` as a `set`, and `d = {}` then `d = {"a": 1}` binds `d` as a `dict`. An annotation supplies an unknown element type at any depth, and for the empty dict too: `ys: list> = [[]]` and `d: dict = {}` bind the annotated type. A constructed list shares the **read surface** of the finite collections — indexing `xs[i]` (including a negative from-end index), slicing `xs[a:b:c]`, `.length`, `for … in`, and membership `x in xs` (§5.3) — and additionally supports equality (§8.1), **concatenation** with another list (`xs + ys`, §8.4), **repetition** by an `int` count (`xs * n`, §8.3), renders in `print` and `+` (§8.4, §8.7), and is produced by a comprehension (§8.9) and by `s.split(...)` (§8.6). Unlike the store-derived collections it exists only while the program runs and is never stored; it is shared and changed in place (§8.14). `list` is not one of the base types — it is a type former over them. **Constructed tuples, dicts, and sets.** Three further **type formers** build finite values alongside `list` — a tuple immutable, a dict and a set shared and changed in place as a list is (§8.14): - `tuple` — a fixed-length **heterogeneous** sequence built by a tuple literal (§3.5.11): `(1, "a")` is a `tuple`. Each position keeps its own type, which a constant index reads; a tuple is **sliced** by constant bounds (`t[1:]` → a sub-tuple, §5.3), **repeated** by a literal `int` count into the repeated position list (`(1, 2) * 2`, §8.3), and compared with `==`/`!=` or ordered with `<`/`>`/`<=`/`>=` against a tuple of any length (§8.1). When every position holds the **same** type it also **iterates** (§6.3), converts (`list`/`set`/`tuple`, §8.13) and is indexed and sliced at a computed position, as a list of its elements is. So does a tuple whose positions are all scalars, whose element is a `value` (§5.6), and one whose positions are all class instances, whose element is their nearest common base (§13.8). A tuple holding a collection beside a value of another type does none of these, since there is no single element type to bind or collect. - `tuple` — a **variadic homogeneous** tuple: a tuple of **runtime** length whose every element is `T`, built by `tuple(iterable)` (§8.13). Because its length is not static it is indexed by a **runtime** `int` (`t[i]` → `T`) and **unpacks as a list does**, its length checked when the statement runs (§8.11); it is **repeated** by any `int` count, literal or not, since it has no arity in its type to name (§8.3); it compares (`==`) against any tuple whose elements agree with `T`. It is homogeneous by construction, so it always **iterates** (§6.3). A **fixed** tuple whose positions all agree with `T` **is** one, a tuple being immutable, so it fills a declared `tuple` wherever one is written: as a value, inside a collection **literal** written at that 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 is shared (§8.14), so its element type stays the one that name has. Fixed tuples of **different** lengths whose elements all share a type `T` are held together as `tuple`, as a type checker joins them: the display `[(1, 2), (3, 4, 5)]` is a `list>`, so it fills that annotation as each of its tuples does on its own. Tuples of one length keep their fixed type, and tuples whose elements share no type are not held together. - `dict` — a **mapping** from a hashable key type `K` (a scalar, or a tuple whose positions are recursively hashable) to a value type `V`, built by a dict literal (§3.5.12) or a dict comprehension (§8.9), kept in insertion order. - `set` — a collection of **unique** hashable elements (a scalar, or a tuple whose positions are recursively hashable), built by a set literal (§3.5.13) or a set comprehension (§8.9), presented in sorted order. All three, like `list`, exist only while the program runs and are never stored, and none is one of the base types. A key or set element is **hashable**: a scalar value class (every class a cell value has, §5.5), a `value`, or a tuple whose positions are recursively hashable. A list, dict or set is unhashable and is not itself a key or set element. Writing one where a key or an element is held is told in Python's words, `unhashable type: 'list'`, and a tuple is asked of what it holds. Looking one up is told the same as the program runs, whatever the dict or the set holds, since Python hashes first: `[1] in d`, `d.get([1], 0)`, `d.pop([1], 0)`, `d[[1]]`, `del d[[1]]`, `s.discard([1])`, `s.remove([1])`. A set asked after in a set is read as the frozen set it spells, so `{1} in {1}` is `False`. A range is the list it is built as (§8.6), and is told as one. **Iterators.** `iterator` is one further type former: a **one-pass reader** over a collection, returned by `zip`, `enumerate`, `reversed` and `iter` (§8.6), which states what reads one. It gives each element once, has no length and no position, and is equal only to itself. Like the three above it exists only while the program runs, and is not a base type. It is not a key or set element either, where CPython hashes an iterator by its identity. A key or set element here is held by its value, and an iterator has none to hold it by, since it is equal only to itself: a set is walked in sorted order (§3.5.13), where an iterator has no place, and CPython prints a set or a dict of iterators with each one's address, which no other run repeats. **Dict views.** `dict_keys`, `dict_values` and `dict_items` are the **views** a `dict` answers from `.keys()`, `.values()` and `.items()` (§8.6), as CPython's are. A view reads its dict **when it is read**, not when it was made: after `ks = d.keys()` and `d["b"] = 2`, `ks` holds `"b"`. It walks the dict's keys, its values or its `tuple` pairs in insertion order — for `for` (§6.3), a comprehension, a conversion (§8.13) and every builtin that reads a collection — has a length (`len`, `.length`) and a truth value (§8.2), and tests membership (§8.1). It has no position: `d.keys()[0]` is a type_error, and `list(d.keys())[0]` returns the first key. A keys view and an items view are **set-like**: each equals a set holding the same elements, takes the set operators beside any collection, on either side, and takes the inclusion order beside a set or another such view (§8.1, §8.3). A values view is equal only to itself. A view prints as CPython's does, `dict_keys(['a', 'b'])`. Like an iterator it exists only while the program runs, and is not a base type, and it is not a key or set element, where CPython hashes a values view by its identity, for the reason an iterator is not one. The base types do not include an absence-of-value type. Absence is asserted through the `assert` verb (§7.1), which carries no value argument — so the absence semantic stays implicit in the verb head-word rather than being represented as a typed value. Since a name holds one type (§6.1), no name can hold "a value or nothing" either, which is why DKE Python has no `None` (§3.5.4). ### 5.2 Field access The expression `.` is well-typed when the table below has a row for the value's type and field; otherwise it is a type_error. `` is any expression (a postfix in the grammar, §4.4), so a field accessor composes on a **computed** value — a method result, an index, or a slice — not only on a bare name: `text.upper().length`, `xs[0].value`, and `history[-1].source` are all well-typed reads. (A store read remains bound to a name first, §9.1: a verb call is not itself a value expression, so `current(K.s.a).value` is written as two lines — `c = current(K.s.a)` then `c.value`.) | Type | Field | Field type | |------|-------|------------| | `active_claim` | `value` | `value` (§5.6) | | `active_claim` | `source` | `string` | | `active_claim` | `path` | `string` | | `active_claim` | `claim_id` | `string` | | `active_claim` | `created_at` | `string` | | `active_claim` | `trust` | `string` | | `active_claim` | `preferred` | `bool` | | `active_claim` | `maintained` | `bool` | | `active_claim` | `written_by` | `string` | | `active_claim` | `derived_by` | `string` | | `active_claim` | `rule_kind` | `string` | | `active_claim` | `valid_from` | `value` (§5.6) | | `active_claim` | `valid_to` | `value` (§5.6) | | `active_claim` | `qualifiers` | `dict` (§3.5.12) | | `proof_tree` | `root` | `active_claim` | | `proof_tree` | `truncated` | `bool` | | `verify_result` | `match` | `bool` | | `verify_result` | `actual` | `string` | | `verify_result` | `expected` | `string` | | `verify_result` | `conflicted` | `bool` | | `refuse_info` | `reason` | `string` | | `refuse_info` | `teaching_hint` | `string` | | `refuse_info` | `discriminator` | `string` | | `engine_error_info` | `reason` | `string` | | `engine_error_info` | `code` | `string` | | `complex` | `real` | `real` | | `complex` | `imag` | `real` | | `int` | `real` | `int` | | `int` | `imag` | `int` | | `int` | `numerator` | `int` | | `int` | `denominator` | `int` | | `bool` | `real` | `int` | | `bool` | `imag` | `int` | | `bool` | `numerator` | `int` | | `bool` | `denominator` | `int` | | `real` | `real` | `real` | | `real` | `imag` | `real` | | `slice` | `start` | `value` (§5.6) | | `slice` | `stop` | `value` (§5.6) | | `slice` | `step` | `value` (§5.6) | Every number has the two parts CPython gives it. A number is its own real part, a `bool` read as the `int` it is, so `True.real` is `1`; its imaginary part is a zero of its class, `0` for an `int` or a `bool` and `0.0` for a `real`. A claim's `value` (§5.6) has neither until it is narrowed: it may hold a string, and what takes only a number takes a `value` only once a `match` or a test has said it holds one. Two types in the table are ones a program cannot write: `refuse_info` and `engine_error_info` arise only as the binding of a `try` handler (§5.4), and are listed here under the names `.kind` reports for them. Field access on them is governed by this table like any other type. `claim_id` names the claim: its cell path followed by `#` and a code of twelve lowercase hexadecimal digits (`Cat.felix.color#3f09c1a27b44`). The code is made from what the claim shows — its value and its type, where it came from (`source`, or the rule that concluded it), its validity window, its qualifiers and its `created_at` — so a claim carries the same `claim_id` on every read that returns it, and writing, revising or removing another claim leaves it unchanged. A claim recorded anew has a `claim_id` of its own: the new claim of a revision (the claim it supersedes keeps its id), a conclusion a rule draws again, and a value in force the `restore` tool brings back — an older version it puts back into the history keeps its `claim_id`, since it keeps its `created_at`. Two claims at one cell that agree on all of those fields share a `claim_id`. `dependents`, `premises`, `forget` and `would_forget` (§7.1) take a `claim_id` in place of the claim it names. `written_by` names the store key that wrote the claim. It is derived from the credential the call was authenticated with — never from anything the program or the caller states — so unlike `source` it cannot be asserted, only observed. A store key identifier is never empty, so a claim with a writer is always distinguishable from a claim without one. `written_by` is empty when **no key wrote the claim**, which happens two different ways: the write was not authenticated, or nothing wrote the claim at all because a rule concluded it (§14). `trust` is what tells those two apart, and the distinction matters — a derived claim has no writer because there was no write, not because a write went unattributed. `valid_from` and `valid_to` report the **validity window** the claim was written with (§7.1) — the stretch of world-time the claim says something about, as distinct from when it was recorded. A **conclusion** reports the window it inherits from its premises (§14.4): the overlap of the windows of every premise it was drawn from. Both are typed `value` rather than `datetime` because a bound may be **open**. A claim valid from a date with no stated end has a `valid_to` of the `null` class (§5.6), and a claim written with no window at all has `null` for both. So a program reads these fields by matching, not by comparing: there is no sentinel instant standing for "no bound", and a program that compared against one would be reading a date the store does not hold. Only the `datetime` and `null` classes ever arise here, but a `match` still needs a `case default:` arm, since exhaustiveness (§6.2) is checked against the whole union. ``` # claim-window.dpy — a claim reports the window it is valid in, and a # conclusion reports the overlap of the windows it was drawn from. @rule def flagged(): for e in Emp: if e.status == "active" and e.dept == "eng": e.flagged = True def show(label: string, c: active_claim): match c.valid_to: case datetime as d: print(label + " until " + str(d)) case null: print(label + " has no end") case default: pass def window_report(): remember(Emp.e1.status, "active", "hr", datetime("2020-01-01T00:00:00Z"), datetime("2022-12-31T00:00:00Z")) remember(Emp.e1.dept, "eng", "hr", datetime("2021-01-01T00:00:00Z"), datetime("2023-12-31T00:00:00Z")) remember(Emp.e2.ongoing, "yes", "hr", datetime("2021-01-01T00:00:00Z"), null) a = current(Emp.e1.dept, datetime("2021-06-01T00:00:00Z")) show("dept", a) d = current(Emp.e1.flagged, datetime("2021-06-01T00:00:00Z")) show("flagged", d) o = current(Emp.e2.ongoing) show("ongoing", o) ``` ``` OK script window_report(): 3 claims written, 3 prints remember Emp.e1.status = "active" valid_from=datetime("2020-01-01T00:00:00Z") valid_to=datetime("2022-12-31T00:00:00Z") remember Emp.e1.dept = "eng" valid_from=datetime("2021-01-01T00:00:00Z") valid_to=datetime("2023-12-31T00:00:00Z") remember Emp.e2.ongoing = "yes" valid_from=datetime("2021-01-01T00:00:00Z") print dept until 2023-12-31T00:00:00Z print flagged until 2022-12-31T00:00:00Z print ongoing has no end ``` `dept` is valid until 2023-12-31 and the conclusion drawn from it only until 2022-12-31, because the other premise stops there. That difference is the whole of what these two fields add: an as-of read (§7.1) tells you whether a claim is in force at an instant you name, one instant at a time, and these say where the window ends without your having to find it by asking. `qualifiers` reports the **facts the writer stated about this claim** when it wrote it — how the value was obtained, how sure the writer is of it, what it was measured with: whatever the writer chose to say, as a dict from name to value. They are written with the `qualifiers=` argument of `remember` and `update` (§7.1), and they belong to the one claim they were written with. Another source's claim at the same attribute carries its own, and a claim written with none reports an empty dict. The store keeps them with the claim, returns them on every read that returns the claim, removes them with it, and never acts on one. A qualifier named `confidence` is what its writer said about its own value, not a judgement the store has made: the store's account of how a claim arrived is `trust`, and no qualifier changes it. Each value keeps its class (§5.6), exactly as `value` does — a qualifier written as `0.8` reads back as a `real` and one written as `datetime("…")` as a `datetime` — and the names come back in name order. ``` # claim-qualifiers.dpy — a writer states facts about its own claim, and every # read that returns the claim gives them back. def qualifier_report(): remember(Reading.r1.temp, 21, "lab", qualifiers={"method": "observed", "confidence": 0.8}) remember(Reading.r1.temp, 23, "forecast", qualifiers={"method": "predicted"}) remember(Reading.r2.temp, 19, "lab") readings = get(Reading.r1.temp) for r in readings: q = r.qualifiers print(r.source + " says " + str(r.value) + ", " + str(q["method"])) plain = current(Reading.r2.temp) none = plain.qualifiers print("r2 carries " + str(len(none)) + " qualifiers") ``` ``` OK script qualifier_report(): 3 claims written, 3 prints remember Reading.r1.temp = 21 qualifiers={"confidence", "method"} remember Reading.r1.temp = 23 qualifiers={"method"} remember Reading.r2.temp = 19 print lab says 21, observed print forecast says 23, predicted print r2 carries 0 qualifiers ``` The two sources disagree about `r1`, and each claim still says how it was reached. That is the point of putting the account on the claim rather than beside it: a program weighing the two reads both values and both accounts from one place, and nothing about either was lost when the other was written. `maintained` is `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 since. It is `True` for every asserted claim and for every derivation whose rule is still keeping its value current. Nothing about an unmaintained claim is in disagreement with anything: it is neither a conflict nor a matter of preference (§7.6), and it continues to be served by every read. The store says the value is not being kept up rather than letting it pass as current, and a reader guards on it directly: ``` c = current(Totals.all.sum) match c: case active_claim: if not c.maintained: print("this total is not being kept current") case empty: pass ``` The mark is established when the claim is read, not recorded on it, so it appears as soon as the rule stops keeping the value current and clears on its own once that is no longer so. A program must therefore read it alongside the value it qualifies rather than carrying it forward: a `maintained` kept from an earlier read describes the earlier read. `truncated` is `True` when a `proof_tree` shows **less than the whole derivation** — the tree is cut off rather than complete. A proof that ended and a proof that was cut short are otherwise indistinguishable, and a cut one reads as whole, so a program that treats a derivation as exhaustive checks this first. `trust` says how the claim came to hold its value. It is one of two words: `asserted` — someone recorded this value — or `derived` — a rule concluded it. Like `written_by` and unlike `source`, it is established by the engine and cannot be written: a caller may cite the literal string `"derived"` as a `source`, and that claim still reads `trust` as `asserted`, because citing a word is not the same as being concluded by a rule. Together with `derived_by` the two fields separate the three cases a reader meets: | `trust` | `written_by` | `derived_by` | what happened | |---------|--------------|--------------|---------------| | `asserted` | a key | empty | someone recorded this value, and this key did it | | `asserted` | empty | empty | someone recorded this value, without an authenticated key | | `derived` | empty | a rule | no one recorded it — the named rule concluded it | `derived_by` names what concluded a derived claim. A rule (§14) reads as the name `list(target = rules)` lists it under (§14.7) — `heat.comfy` for the rule `comfy` in the module `heat`, `warm` for a rule compiled on its own — and a computed field (§13.7) as its class and its field, qualified by its module the same way — `heat.Box.area`. It is the empty string on a claim someone recorded. `premises(c)` (§7.1) returns the claims the conclusion was drawn from. `rule_kind` is the kind of relation the concluding rule states, when its author said (§14.1) — `"causal"` for a rule written `@rule(kind="causal")` — and the empty string otherwise: on a claim someone recorded, on a computed field's conclusion, and on a conclusion of a rule written without one. Asking who wrote a derived claim has no answer. Asking *why it holds* does: `why_not(K.s.a)` and `what_needs(K.s.a)` answer about a conclusion that is **not** there. `why` requires a claim to explain; when a read comes back empty the question is a different one, and these two answer it. `why_not` names the condition that blocked the conclusion and distinguishes eight cases that are otherwise one observable: a condition never recorded, a condition recorded whose validity window does not cover the time asked about, a condition recorded that fails its test, a condition recorded as absent (`assert`), a condition recorded with a value that has no order under an ordered test (a blob, or a recorded absence), a condition recorded with a value that has an order and compared under an ordered test with one that has none, a condition required to be absent that is present, and a condition whose sources disagree. A recorded absence holds no value, so a rule that only reads the field is told it is absent, not that it failed a comparison. A condition comparing two fields (`o.qty > o.limit`) is stated with both, as it was written. A value with no order is not a value that failed, on either side of the test: no value of its type passes `<`, `<=`, `>` or `>=`, so what it needs is a different value, or `==`. The value compared with is named when the rule read one value for it. `what_needs` names the facts that would make the conclusion hold, walking through conditions the standing rules can themselves derive and stopping at the ones only the caller can supply. When a rule has every condition it needs and still concludes nothing — a fold of the rule cannot take the values it reads (§14.9, `declined`), the comparison it concludes cannot rank the values it reads, or the arithmetic it computes has no answer for them (a divisor of zero, say) — both verbs say so of that rule, the last with the operation over the values it read and the fields they came from, and `what_needs` says it before the conditions it lists as recorded. A condition whose cell already holds a value is named as recorded, with the case `why_not` would give it (out of its window, failing its test, recorded as absent, without an order, compared with a value without one, or disagreed on), rather than as a fact to supply. A condition required to be absent whose field holds a value is named as present, never as a fact to supply. The move is removing the value or narrowing its window where the caller recorded it, and changing what the concluding rule reads where a rule concludes it, since a conclusion `forget` removes is drawn again while its premises hold (§14.4). The walk does not go through the rules that would conclude such a field, since what they need would make it hold, and a condition required to be absent that is absent is listed as held. Both are reads and write nothing; both answer `no standing rule concludes this` when no rule produces the cell at all, which is a different finding from an unmet condition and usually a more useful one. Where the cell asked about itself holds a claim whose validity window does not cover the time asked about, that is reported first, before anything the standing rules have to say: the fact is recorded and out of force, so what it needs is a wider window or an as-of the window covers rather than another write. Both take no `as_of`: a diagnosis is about the store as it stands. `why(K.s.a)` returns the derivation (§7.1), which is the account of a derived claim in the same way a key is the account of a written one. Anchored, `why(K.s.a, datetime("…"))` returns the derivation **as it stood at that instant**, built from the premises whose validity windows covered it. This is what explains a changed answer: a conclusion can hold at one instant and not at another because a premise moved, and asking the same cell at two instants gives two derivations whose difference names the premise that moved. A `proof_tree` is a finite collection (§5.1) whose elements are `active_claim`s, so a program can hold both and compare them. An anchored derivation may be absent where the unanchored one is not, and the reverse. Absence here is the `empty` discriminator (§5.1) rather than a zero-length `proof_tree`, so the collection surface is for an answer already established to be a proof — by `match`, whose case set for a `proof_tree` is `{ proof_tree, empty }` (§6.2). 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` explains the absence. `source` and `written_by` answer different questions and are not interchangeable. `source` is the origin the writer *cited* for the content — a claim about the data, which the engine records and cannot check. `written_by` is *who performed the write* — a fact about the act, established by authentication. A claim may cite a trusted registry and have been written by anyone holding a key to the store. `path` is the claim's cell path (`Cat.felix.color`) — the `claim_id` without its `#` and code. It is most useful for the reads whose claims live at cells the call site did not name: `dependents` and `premises` (the cell each claim they return lives at) and `conflicts` (each conflicting cell). `path` and `claim_id` are spelled the way a program addresses the cell, so a kind a module declares reads back qualified by its module — `inventory.Item.i1.qty` — as §7.1's listings name it. `created_at` is the claim's creation moment: the UTC wall-clock instant at which the claim was recorded, rendered as an RFC 3339 timestamp with microsecond precision and a literal `Z` suffix — e.g. `2026-07-04T09:15:42.123456Z`. The value is fixed when the claim is created and is the same string no matter how the claim was obtained (`current` and `get` agree). A claim with no recorded creation moment carries the empty string. Lexicographic comparison of two non-empty `created_at` values orders them chronologically (RFC 3339 UTC is sortable). Two claims recorded within the same microsecond carry the same `created_at`, so equal values mean *recorded in the same microsecond*, not *recorded at the same moment*, and the field does not say which of the two came first. Sorting by `created_at` is a correct chronological sort in which such claims tie. `created_at` **orders claims; it does not measure elapsed time.** Ordering is the whole of what it guarantees: given two claims with different stamps, it tells you which was recorded first. The difference between two `created_at` values is **not** the time that passed between them, and no reading of it as a duration is supported — when two claims are recorded in quick succession their stamps may differ by an amount unrelated to the interval between them. This is why `created_at` is a `string` and not a `datetime`: temporal arithmetic on it is rejected at compile time rather than producing a plausible wrong answer. To record how long something took, measure it with your own clock and store the result as a `duration` (§3.5) — that value means what it says, composes with `datetime` arithmetic, and is what the aggregate folds are defined over. **Universal accessors**: - `.kind` returns `string` for any discriminator value (`active_claim`, `proof_tree`, `empty`, `verify_result`, and the try-handler discriminators of §5.4). The returned string is the type name as written in §5.1. - `.length` returns `int` for any `string`, any `blob` or any finite collection. For a `string` the result is the number of characters (Unicode characters, not bytes); for a `blob` it is the number of bytes; for a collection it is the number of elements. The Python builtin **`len(x)`** is accepted as an alias — `len(x)` is exactly `x.length` (and a type_error when `x` is none of these). ### 5.3 Indexing and slicing **Indexing.** The expression `[]` selects one element. It is well-typed when the value has one of the types in this table, and the result type is the one the table gives: | Value type | Element type | |-----------------|--------------| | `claim_list` | `active_claim` | | `proof_tree` | `active_claim` | | `diagnosis` | `string` | | `string_list` | `string` | | `subject_set` | `string` | | `result_type_set` | `string` | | `string` | `string` | | `list` | `T` | | `tuple` | the indexed position's type | | `dict` | `V` (indexed by a key of type `K`, not an `int`) | **A set does not index.** A `set` has no positional index, in Python or here, so `s[i]` and `s[a:b]` are refused before the run, in Python's words: `'set' object is not subscriptable`. A set's elements are read with a bounded `for`, which walks them in sorted order (§3.5.13), and one is tested with `in` (§8.1). **Tuple indexing.** A tuple's positions may differ in type, so `t[i]` with a **compile-time constant** integer `i` (a literal, an optional leading minus, or a constant `+`/`-` of such) has the type of that position: `t[0]` the first, `t[-1]` the last. A computed index reads what a `for` over the tuple binds (§5.1): the positions' one type, `value` over scalars of several (§5.6), or the nearest common base of class instances. Over a tuple holding a collection beside a value of another type a computed index is a type_error, which costs no function, every position being reachable by a literal index. An index past either end compiles, and refuses when it runs, catchably, as Python raises its `IndexError` there (§10.3), so `t[9]` on a pair is a catchable refusal at run time, saying `tuple index out of range`. A constant one is typed as an element of the tuple read as a list, the positions' one type or `value` over scalars of several (§5.1), so what reads it is checked as it would be against such an element. This is one application of a general rule (§8.3, *Value-dependent typing*), not a rule of its own. **Dict indexing.** `d[k]` looks a value up by key, comparing `k` with the keys as `==` does, and the result is `V`. A key absent at run time is a catchable refuse (§10.3), and so is a key of another type, which no key equals (`{1: 2}["a"]` refuses as `'a'`). Because dict keys are scalars (not necessarily `int`), dict indexing is resolved by the key type rather than the `` rule below. Indexing a `string` returns the one-character string at that position; positions count characters (Unicode characters, not bytes), starting at `0`. A **negative** index counts from the end: `s[-1]` is the last character, `s[-2]` the one before it, down to `s[-len]` (the first). Negative from-end indexing is defined for a `string` and for a **constructed `list`** (`xs[-1]` is the last element); a **store-derived** collection (§5.1) takes a non-negative index only. An index still out of range after the from-end shift — of a string or a constructed list, or a *non-negative* index of a store-derived collection — is a runtime refuse (§10.3), not a static error. The index expression MUST be of type `int`. An index of another type is a type_error, told in Python's words over a `string`, a `list` or a `tuple`, naming the index's class: `list indices must be integers or slices, not str` for `[1]["a"]`, and `string indices must be integers, not 'float'` for `"ab"[1.5]`. A `del` or a write through such an index says the same, and a list's `pop` given one says `'str' object cannot be interpreted as an integer`. An index of a type only this language has keeps this language's words. **Slicing.** The expression `[:]` returns the substring from character position `` up to (but not including) position ``. Both bounds are optional: `s[:]` runs to the end of the string, `s[:]` starts at the beginning, and `s[:]` is the whole string. Each bound that is present MUST be of type `int`. Slicing applies to a `string` (result `string`) and to a **constructed `list`** (result `list` — a slice yields a new list of the same element type, which a change to the source does not reach, §8.14); it does not apply to the store-derived collections. **Step.** A slice may carry an optional third component — the **step** — `[::]`. The step (an `int`, default `1`) selects every ``-th character. A **negative** step walks the string in reverse: `s[::-1]` is `s` reversed, and `s[::-2]` takes every other character from the end. When a step is present the bound defaults follow the step's direction: a positive step defaults `` to the beginning and `` to the end, while a negative step defaults `` to the last character and `` to before the first. A step of `0` is a catchable runtime **refuse** (`slice_step_zero`); it is the only non-total slice. A **negative** bound counts from the end, like indexing: `s[-3:]` is the last three characters, `s[:-1]` all but the last. A negative bound past the start of the string resolves to `0`. Slicing is otherwise **total** — it never refuses. A bound beyond the ends of the string is clamped to it, and a start at or past the end (or at or past the end bound) yields the empty string `""`. Positions count characters, consistent with indexing and `.length`. A negative *step* is the reverse direction; a negative *bound* is a from-end position — the two are independent (`s[-1:-4:-1]` walks the last three characters backwards). A **constructed `list`** slices by the identical rules — optional bounds, an optional step (with `xs[::-1]` reversing), clamped out-of-range bounds, negative from-end bounds, and the step-`0` refuse — producing a new `list`. `xs[a:b]` selects the elements at positions `a` up to (but not including) `b`; positions count elements, consistent with list indexing and `.length`. A **`tuple`** slices by the same rules and **yields a tuple** (Python's `t[a:b]` → `tuple`). With **constant integer** bounds (`t[1:]`, `t[:2]`, `t[1:-1]`, `t[::-1]`) the result type is the sub-tuple of the selected positions. With a computed bound it is a variadic `tuple` of what a computed index reads, and over a tuple holding a collection beside a value of another type a computed bound is a type_error, as a computed index is (§5.3). `list(t[a:b])` (§8.13) then converts the result to a `list` — the idiom behind starred unpacking. **A slice as a value.** `slice(stop)`, `slice(start, stop)` and `slice(start, stop, step)` build a value of type `slice` (§5.1) that holds three bounds. Each argument is an `int`, a `bool` (kept as written, as CPython keeps it) or `None` (§3.5.4), which leaves that bound open, and a bound not given is open too, so `slice(5)` is CPython's `slice(None, 5, None)`. A slice indexes as its bounds written in brackets do: `xs[s]` answers what `xs[a:b:c]` answers on a `string`, a `list` and a variadic tuple. A zero step is refused there as the bracketed form refuses it. On a fixed-length tuple a slice is a bound the compiler does not see, so `t[s]` reads as a computed bound does: a variadic tuple of what a computed index reads, and a type_error over a tuple holding a collection beside a value of another type. On a list, `xs[s] = c`, `xs[s] op= c` and `del xs[s]` change what the bracketed slice would (§8.14). A slice carries: | Member | Signature | Result | |--------|-----------|--------| | `s.start`, `s.stop`, `s.step` | `value` (§5.6) | the bound as it was given; an open bound reads back as the `null` class | | `s.indices(n)` | `(int) → tuple` | the `(start, stop, step)` that `xs[s]` selects from a sequence of length `n`, as CPython's `slice.indices` answers it: bounds counted from the end when negative and clamped to the sequence, and a step of `1` when open. A negative `n` is refused `length should not be negative` (`value_error`), and that is checked before the step; a zero step is refused `slice step cannot be zero` (`slice_step_zero`) | A slice prints as CPython prints it, `slice(1, 5, None)`, through `print`, `str`, `repr`, an f-string hole and inside a collection. Two slices are `==` when their three bounds are, so `slice(1, 2) == slice(True, 2)` holds as it does in CPython. A slice is not ordered, since CPython orders slices by their bounds and fails wherever one is `None`. It is always true and has no `.length`, and it is not hashable, as in CPython 3.11, so it is neither a `set` element nor a `dict` key. It is a value a program builds rather than a fact: no field and no verb stores one. One difference from CPython: an open bound read back prints `null`, as an open window bound does (§3.5.4), where CPython prints `None`. `print(slice(3).start)` writes `null`, while the slice itself prints `slice(None, 3, None)`. **String methods.** A `string` value carries these built-in methods: | Method | Signature | Result | |--------|-----------|--------| | `s.find(sub[, start[, end]])` | `(string[, int[, int]]) → int` | the character position of the first occurrence of `sub` in `s`, or `-1` if absent; the optional `start[, end]` window searches `s[start:end]` and the result is an index into the whole `s` (negative bounds count from the end) | | `s.rfind(sub[, start[, end]])` | `(string[, int[, int]]) → int` | the character position of the **last** occurrence of `sub` in the `start:end` window, or `-1` if absent | | `s.index(sub[, start[, end]])` | `(string[, int[, int]]) → int` | like `find`, but **refuses** (§10.3, `substring_not_found`) instead of returning `-1` when `sub` is absent from the window | | `s.rindex(sub[, start[, end]])` | `(string[, int[, int]]) → int` | like `rfind`, but **refuses** (`substring_not_found`) when `sub` is absent from the window | | `s.count(sub[, start[, end]])` | `(string[, int[, int]]) → int` | the number of non-overlapping occurrences of `sub` in the `start:end` window; an empty `sub` yields the window's `length + 1` | | `s.startswith(prefix[, start[, end]])` | `((string \| tuple)[, int[, int]]) → bool` | `True` if the `start:end` window of `s` begins with `prefix` (an empty `prefix` yields `True`). `prefix` may instead be a **tuple of strings** — `True` if the window begins with **any** of them (an empty tuple yields `False`) | | `s.endswith(suffix[, start[, end]])` | `((string \| tuple)[, int[, int]]) → bool` | `True` if the `start:end` window of `s` ends with `suffix` (an empty `suffix` yields `True`). `suffix` may instead be a **tuple of strings** — `True` if the window ends with **any** of them (an empty tuple yields `False`) | | `s.replace(old, new[, count])` | `(string, string[, int]) → string` | a copy of `s` with each non-overlapping occurrence of `old` replaced by `new`, at most `count` times when given (default: all); an empty `old` inserts `new` in each gap | | `s.upper()` | `() → string` | `s` with each character replaced by its full Unicode uppercase — a character may expand to several (`ß`→`SS`, `fi`→`FI`), so the result can be longer than `s` | | `s.lower()` | `() → string` | `s` with each character replaced by its full Unicode lowercase (a character may expand, `İ`→`i̇`); a Greek `Σ` becomes word-final `ς` when it ends a word (preceded by a cased character, not followed by one), else `σ` | | `s.capitalize()` | `() → string` | `s` with its first character titlecased and the rest lowercased | | `s.title()` | `() → string` | `s` with the first character of each word titlecased and the rest lowercased — a word is a maximal run of cased characters, so any non-letter (space, digit, apostrophe) starts a new word | | `s.swapcase()` | `() → string` | `s` with each uppercase character lowercased and each lowercase character uppercased (the full mapping above; the lowercasing applies the word-final `Σ`→`ς` rule) | | `s.casefold()` | `() → string` | `s` case-folded for caseless matching — the full Unicode case-folding mapping, more aggressive than `lower` (`ß`→`ss`) and **context-free** (both `Σ` and `ς` fold to `σ`; NO word-final rule, so `"ΟΔΟΣ".casefold()` is `"οδοσ"`, unlike `.lower()`'s `"οδος"`) | | `s.strip([chars])` | `([string]) → string` | `s` with leading and trailing characters removed — whitespace when `chars` is omitted, otherwise any character that occurs in `chars` | | `s.lstrip([chars])` | `([string]) → string` | like `strip`, but removes only **leading** characters | | `s.rstrip([chars])` | `([string]) → string` | like `strip`, but removes only **trailing** characters | | `s.removeprefix(prefix)` | `(string) → string` | `s` with a leading `prefix` removed if `s` begins with it, otherwise `s` unchanged | | `s.removesuffix(suffix)` | `(string) → string` | `s` with a trailing `suffix` removed if `s` ends with it (and `suffix` is non-empty), otherwise `s` unchanged | | `s.expandtabs([tabsize])` | `([int]) → string` | `s` with each tab replaced by spaces to advance to the next tab stop (a multiple of `tabsize`, default `8`); the column counts characters and resets to zero after each `\n` or `\r`. A `tabsize ≤ 0` removes tabs | | `s.zfill(width)` | `(int) → string` | `s` left-padded with `'0'` to `width` characters; a leading sign (`+`/`-`) stays first and the zeros follow it. `width ≤ s.length` returns `s` unchanged | | `s.ljust(width[, fill])` | `(int[, string]) → string` | `s` left-justified in a field of `width` characters, padded on the right with `fill` (a single character, default `' '`) | | `s.rjust(width[, fill])` | `(int[, string]) → string` | `s` right-justified — padded on the **left** with `fill` | | `s.center(width[, fill])` | `(int[, string]) → string` | `s` centred in a field of `width` characters, padded with `fill`; when the padding cannot be split evenly the extra character goes on the **left** iff both the margin and `width` are odd (CPython's rule). `width ≤ s.length` returns `s` | | `s.split([sep[, maxsplit]])` | `([string[, int]]) → list` | the substrings of `s` split at each non-overlapping occurrence of `sep` (empty pieces kept); an optional `maxsplit` caps the number of splits (≤ `maxsplit + 1` pieces); with **no** argument, `s` is split on runs of whitespace with empty pieces dropped and leading/trailing whitespace ignored. Naming `maxsplit` and not `sep` splits on whitespace too, at most `maxsplit` times, and keeps the rest as one piece (**Arguments by name**, below) | | `s.rsplit([sep[, maxsplit]])` | `([string[, int]]) → list` | as `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`. Naming `maxsplit` and not `sep` takes whitespace pieces from the right | | `sep.join(xs)` | `(iterable of string) → string` | the strings `xs` yields concatenated with the separator `sep` between each adjacent pair: a `string`'s characters, the elements of a `list`, `tuple`, `set` or iterator of strings, or a `dict`'s string keys (an empty one yields `""`) | | `s.partition(sep)` | `(string) → tuple` | the part of `s` before the **first** occurrence of `sep`, `sep` itself, and the part after (`"a@b@c".partition("@")` is `('a', '@', 'b@c')`); when `sep` is absent from `s` the whole string lands in the **first** position and the other two are `""`; an **empty** `sep` refuses (§10.3, `empty_separator`) | | `s.rpartition(sep)` | `(string) → tuple` | as `partition`, but split at the **last** occurrence (`"a@b@c".rpartition("@")` is `('a@b', '@', 'c')`); when `sep` is absent the whole string lands in the **third** position — not the first — and an empty `sep` refuses alike | | `s.translate(table)` | `(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` finds a code point as any `dict` does, so `97.0` is the key `97` and a string key finds nothing, as in Python. A `list`, a `tuple` or a `string` is indexed by the code point, and a character past its end stays. A table of any other type, reached by a character, is a catchable refuse (§10), and so is a value of any other type, a code point outside `range(0x110000)` or a surrogate, when a character reaches it | | `s.format(*args, **kwargs)` | `(any…) → string` | `s` read as a template, as CPython reads it (*String templates*, below), each field replaced by what it names; a `*` spreads positions and a `**` names. An argument no field reaches is no fault | | `s.format_map(m)` | `(any) → string` | as `format`, every name looked up in `m` and no field a position | | `s.splitlines([keepends])` | `([bool]) → list` | the lines of `s` with their terminators removed, split at each of CPython's line boundaries — `\n`, `\r`, `\r\n` (one boundary), `\v`, `\f`, `\x1c`, `\x1d`, `\x1e`, U+0085, U+2028 and U+2029; a **trailing** line boundary yields no final empty piece (`"a\nb\n".splitlines()` and `"a\nb".splitlines()` are both `['a', 'b']`), a blank line yields an empty piece (`"a\n\nb"` gives `['a', '', 'b']`), and `""` yields `[]`. With a true `keepends` each line keeps the boundary that ends it, `"a\r\nb".splitlines(True)` being `['a\r\n', 'b']`; an `int` there is read for its truth value, as in Python | | `s.isspace()` | `() → bool` | `True` if `s` is non-empty and every character is whitespace | | `s.isdigit()` | `() → bool` | `True` if `s` is non-empty and every character is a digit | | `s.isdecimal()` | `() → bool` | `True` if `s` is non-empty and every character is a decimal digit | | `s.isnumeric()` | `() → bool` | `True` if `s` is non-empty and every character is numeric (includes decimals, fractions, and numeral characters) | | `s.isalpha()` | `() → bool` | `True` if `s` is non-empty and every character is a letter | | `s.isalnum()` | `() → bool` | `True` if `s` is non-empty and every character is a letter or a numeric character | | `s.isupper()` | `() → bool` | `True` if `s` has at least one cased character and none of them is lowercase or titlecase | | `s.islower()` | `() → bool` | `True` if `s` has at least one cased character and none of them is uppercase or titlecase | | `s.istitle()` | `() → bool` | `True` if `s` is titlecased — every uppercase/titlecase character follows an uncased one and every lowercase character follows a cased one, with at least one cased character | | `s.isascii()` | `() → bool` | `True` if every character is in the ASCII range (U+0000–U+007F); the **empty** string is ASCII (`"".isascii()` is `True`) | | `s.isprintable()` | `() → bool` | `True` if every character is printable: not a control, a format character, a private-use or unassigned codepoint, or any separator but the space. The **empty** string is printable (`"".isprintable()` is `True`). Past U+007F these are exactly the characters `repr` writes as themselves (§8.13) | | `s.isidentifier()` | `() → bool` | `True` if `s` is non-empty, its first character is `_` or has the Unicode property XID_Start, and every later one has XID_Continue, as Python decides. A keyword is an identifier too: `"class".isidentifier()` is `True`. | **Arguments by name.** A string method takes by name exactly the arguments CPython lets a caller name, and places each in the position it names, as a built-in function does (§8.13): `sep` and `maxsplit` on `split` and `rsplit`, `keepends` on `splitlines` and `tabsize` on `expandtabs`, so `s.split(sep=",", maxsplit=1)` splits as `s.split(",", 1)` does. A separator left out is Python's `None`, which has no spelling here, so naming `maxsplit` alone is how a whitespace split is bounded: `" a b c ".split(maxsplit=1)` is `['a', 'b c ']`, the rest kept whole after the whitespace that follows the last piece, and `" a b c ".rsplit(maxsplit=1)` is `[' a b', 'c']`, its pieces taken from the right. A dict's `update` takes any name as a key and `sort` takes `key` and `reverse` (§8.14). Every other method of a string, a list, a tuple, a set, a dict, a dict view or a number takes no argument by name, as in Python, and a name given to one is a compile-time error in Python's words, naming the receiver's Python class, such as `str.center() takes no keyword arguments`. A receiver without the method is told that instead, since Python finds the method before it reads an argument. **Arguments of a type a method does not take.** An argument of a type a string's method does not take is a compile-time error in Python's words, which follow how the method is written in C, and so differ by method for one fault: `"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".startswith(1)` with `startswith first arg must be str or a tuple of str, not int`, `"abc".split(1)` with `must be str or None, not int`, `"abc".strip(1)` with `strip arg must be None or str`, `"abc".center(5, 1)` with `The fill character must be a unicode character, not int`, and `"abc".zfill("a")` with `'str' object cannot be interpreted as an integer`. A search's bounds are read as a slice's: `"abc".find("a", 2.5)` is refused with `slice indices must be integers or None or have an __index__ method`, and `[1, 2].index(1, "a")`, over a list or a tuple, with `slice indices must be integers or have an __index__ method`. A list's `insert` position and a slice's `indices` argument are integers, so `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 instead which type the method takes. All character positions and widths count **characters** (Unicode codepoints), not bytes (§5.2). The case transforms (`upper`/`lower`/`capitalize`/`title`/ `swapcase`/`casefold`) apply the **full** Unicode case mapping — a character may map to more than one (so the result, and its `.length`, can grow) — and the forms that lowercase apply the Greek word-final `Σ`→`ς` rule; `casefold` is the sole exception (context-free, no word-final rule). The character-class predicates (`is…`) classify each character by its Unicode properties. Every method is total except `split`, `ljust`/`rjust`/`center`, and `index`/`rindex`: `split` refuses (§10.3, `empty_separator`) only when an **explicit** separator argument is the empty string (the argument-free whitespace form never refuses), `ljust`/`rjust` /`center` refuse (`fill_not_one_char`) when an explicit `fill` argument is not exactly one character, and `index`/`rindex` refuse (`substring_not_found`) when the substring is absent. `s.split` and `sep.join` bridge the `string` and constructed-`list` surfaces: `split` produces a `list` (§5.1) and `join` consumes one. `join` reads any iterable of strings as `for` reads it, as Python does — a `string`, a `list`, a `tuple`, a `set` (in its order, §3.5.12), a `dict`'s keys, a dict view or an iterator, which it reads to the end: `"-".join("abc")` is `"a-b-c"`. An iterable whose elements are not all strings is a type_error, in Python's words where the first that is not is of a class Python has: `"a".join([1])` is refused with `sequence item 0: expected str instance, int found`, and `"a".join(1)`, over what is no iterable, with `can only join an iterable`. These are the only method calls the language defines on a built-in value. `upper()` and `lower()` apply the full case mapping stated above: a character whose case change needs more than one character maps to all of them, so `"straße".upper()` is `"STRASSE"` and `len("ß".upper())` is `2`. `strip()`, `lstrip()`, and `rstrip()` remove leading and/or trailing characters; with no argument they remove whitespace, and with a `chars` argument they remove any character that occurs in `chars` (the `chars` argument is a **set** of characters, not a prefix or suffix). The interior is untouched, and stripping a string down to nothing yields `""`. ### 5.4 Try-handler discriminator types A `try` statement's `except` handlers may each introduce one bound identifier (via the optional `as `, §9.2), carrying a discriminator type the user cannot write as a type annotation. When a handler omits `as ` it binds nothing and the types below simply do not arise for that handler: - **`except refuse as r`** — `r` has fields `reason`, `teaching_hint`, `discriminator`, all of type `string`. `r.kind` returns the literal string `"refuse_info"`. `reason` states what was refused, in the script's natural language. `teaching_hint` carries a suggestion for correcting the call, and is empty when there is none — it is an aid to a human reader, never a second reason. `discriminator` is a short tag naming which refusal this is, for a program that must branch rather than report; a refusal raised by `raise (…)` (§4.3) carries the exception name as its tag. - **`except engine_error as e`** — `e` has fields `reason`, `code`, both of type `string`. `e.kind` returns the literal string `"engine_error_info"`. `reason` states what failed; `code` is a short tag naming which failure this is, in the same role `discriminator` plays for a refusal. Every one of the five fields is a `string` and is always present; the ones that may carry nothing carry `""` (§5.1: the language has no absent value). The **set of tags** `discriminator` and `code` can take is deliberately not specified. Both are readable, comparable, and safe to branch on within a release, but the vocabulary is not a stable contract and this document does not enumerate it: a program that must behave differently for different failures should branch on the tags it has observed and keep a fallback arm. Programs that report rather than branch should use `reason`, which is written for a reader. These types are produced only by their corresponding handlers; they cannot be passed as parameters, returned by verbs, or written in a type annotation. Their fields are in the §5.2 table under the names `.kind` reports. ### 5.5 Type names in source The type names valid as `type-name` tokens are the primitives (`string`, `int`, `bool`), the **value-class scalars** (`datetime`, `duration`, `real`, `blob`, `scientific`, `complex`), and the read-result types from §5.1 (`active_claim`, `claim_list`, `proof_tree`, `diagnosis`, `subject_set`, `string_list`, `verify_result`, `result_type_set`). A parameter or return annotation (§4.2) additionally admits a parameterized **collection type** — `list`, `set`, `dict`, `tuple` — nestable to any depth, and an **iterator type** `iterator` (§8.6), which `typing.Iterator[T]` is taught to write, and the three **dict view types** `dict_keys`, `dict_values` and `dict_items` (§5.1). A collection type is built from these names **or from a class name**, so `list` is a type. **CPython's spellings** are read beside these. `str` and `float` are `string` and `real`, on the surface whose words are CPython's, and a class the program declares by either word is that class; another surface has its own word and teaches it. A collection type takes CPython's brackets as well as angle brackets, so `list[int]` is `list`, `dict[str, int]` is `dict` and `tuple[int, ...]` is `tuple`. They are one type, and a diagnostic renders it with angle brackets. A collection word with nothing after it, as in `x: list = []`, says nothing of what it holds and is refused, teaching `list[int]`, and so are `typing`'s capitalised names, `List[int]` among them, which have no module here to come from. `bytes` is refused, teaching `blob`, since a `blob` prints otherwise. A return annotation of `None` alone, as in `def f() -> None:`, says what no annotation says; `None` as any other annotation is refused. A **class name** the program declares (§13.1) is a type too, and is valid in every position a type is written: a parameter, a binding annotation, a collection's element type, a script's return and a value method's return. It is not a `type-name` token — it enters the grammar at `type`, as an `IDENT` (§4.2) — so the list above remains the list of names the language itself reserves for types, and is not the list of everything writable where a type goes. What a class name means in those positions is §13.8: a subclass instance stands where its base class is named. A **parameter** (§4.2) may be typed by any of them. A value-class-scalar parameter supplied over the wire is given as its canonical text — a `datetime` as RFC 3339 UTC (§3.5.5), a `duration` as ISO 8601 (§3.5.6), a `real` as a decimal with a point (§3.5.7), a `blob` as hex (§3.5.8), a `scientific` as its canonical numeral (§3.5.14), a `complex` as the text it renders as, with or without its parentheses (§3.5.15) — and refuses if the text is malformed. A `complex` is given as `3+4j`, `(3+4j)` or `-2.5j`. A **class field** (§13.1) must be a primitive or a value-class scalar — a `scalar-name`, a type the engine stores as a cell value (`string`, `int`, `bool`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex`) — because a field holds one stored value; the read-result types are not stored values and are not valid field types. The name `empty` is valid as a `match` case label (§6.2) but NOT as a `type-name` token. The names `refuse_info` and `engine_error_info` name the try-handler discriminator types (§5.4) but are NOT writable as `type-name` tokens. The name `value` (§5.6) names the claim-content union; it arises only from `.value` field access and is likewise NOT writable as a `type-name` token. The name `null` (§5.6) is the **absence** value class — it arises only from a `case null:` match arm and, having no payload to pass or store, is NOT a `type-name` token (a parameter or field is never `null`-typed). Type names are contextual identifiers, not reserved words (§3.7). ### 5.6 The `value` union The `.value` field of an `active_claim` (§5.2) has type `value`: the **stored content of a claim, carrying its own type**. A value written as an integer reads back as an `int`; a value written as a boolean reads back as a `bool`; a value written as text reads back as a `string`; a value written as a `datetime("…Z")` datetime literal (§3.5.5) reads back as a `datetime`; a value written as a `duration("…")` duration literal (§3.5.6) reads back as a `duration`; a value written as a `3.5` real literal (§3.5.7) reads back as a `real`; a value written as an `blob("…")` blob literal (§3.5.8) reads back as a `blob`; a value written as a complex number (§3.5.15) reads back as a `complex`. The type is a property of the claim, fixed when the claim is recorded, and is the same however the claim is obtained (`current` and `get` agree, as for every other field). `value` is a **tagged union** over its value classes: ``` value = int | bool | string | datetime | duration | real | null | blob | scientific | complex ``` Every class but one is **write-producible** from DKE Python — a program writes an `int`, `bool`, `string`, `datetime`, `duration`, `real`, `blob`, `scientific` or `complex` and reads it back at that class. The remaining class, `null` (an explicit absent value), is **read-only**: it is asserted through the `assert` verb (§7.1), which carries no value argument, so no value literal produces it — but a claim carrying one is discriminable on read (see below). A program consumes a `value` in one of four ways: - **Destructure it** with a `match` (§6.2) to obtain the typed content: ``` c = current(Sensor.room.count) match c.value: case bool as b: print("flag = " + str(b)) # b : bool case int as n: print("next = " + str((n + 1))) # n : int — arithmetic is well-typed case string as s: print("text = " + str(s)) # s : string case datetime as d: print("time = " + str(d)) # d : datetime — renders RFC 3339 case duration as u: print("span = " + str(u)) # u : duration — renders ISO 8601 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 scientific as s: print("sci = " + str(s)) # s : scientific — renders the canonical numeral case complex as z: print("size = " + str(abs(z))) # z : complex — arithmetic is well-typed case null: print("an absent value") # detect-only — no `as` binding ``` A **write-producible** class (`int`, `bool`, `string`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex`) supports a named `case as :` arm that binds `` to the content **at the arm's class type**, scoped to that arm, so arithmetic and other type-specific operators apply without a cast. This and a test (below) recover an `int` (for arithmetic), a `bool` (for logical operators), or a `datetime` / `duration` / `real` / `blob` / `scientific` / `complex` (to render, compute with or re-store, §8.4) from a claim's content. The one **read-only** class, `null`, is written as a **bare** `case null:` arm — it discriminates the class but binds no payload; attaching `as ` to it is a type_error (an absent value has no content to bind). - **Narrow it** with a test. Where a test has shown what a name holding a `value` holds, the name is read at that class: in the branch an `isinstance` test takes (§13.9), after the test in an `and`, after a comprehension's `if` making it, in a conditional expression's first branch, in an arm of a `match` naming its class (§6.2), and in the body of an arm whose guard tests it, as CPython runs each. `isinstance(v, int)` reads `v` as an `int`, and so does a test of `int` beside `bool`, since a `bool` is one; a test of either beside `real` reads it as a `value` holding a number, and an `or` of two tests reads it as what the two join to. Only a name is narrowed, not a field read such as `c.value`. A `value` built from scalars holds their classes and no other: `[1, "a"]` holds an `int` or a `string`, and so does an element read from it, a loop variable over it and a join of two such values. **Where a test fails**, a name holding one is read at the classes left to it: in the `else` of an `isinstance` test, in the branch a negated test takes, in a conditional expression's second branch, after the test in an `or`, and in an arm of a `match` below arms naming classes (§6.2). A `value` whose classes are not known, such as a claim's, may be any class, so a failed test leaves it a `value`; so does the `else` of an `and`, whose left test may have held. A branch no `value` can take is never run and is not read: a test of types no `value` has (`isinstance(v, list)`), two tests no class passes both, or the `else` of a test of `object`. An operator meeting a `value` of known classes runs where it takes each class beside the other operand, and answers what their answers join to, so `x * 2` over a `value` holding an `int`, a `real` or a `string` is a `value` of the three; a name read on both sides meets only a class with itself. ``` for e in (1, 2.5, "a"): if isinstance(e, int): print(e + 1) # e : int elif isinstance(e, str): print(e.upper()) # e : string else: print(e * 2) # e : real ``` - **Render it** directly. A `value` is expressible: `print()` appends its textual rendering (§8.7), and `str(…)` renders it (`"count=" + str(c.value)` → `string`, §8.4). Rendering never requires knowing the class. - **Compare it** with `==` / `!=` against any value — a literal of any of its classes, another `value`, a collection (§8.1). The result is always `bool`; a class mismatch compares unequal rather than coercing (`c.value == 41` is `False` when the content is the text `"41"`). A value match covers every class named above, **or** any subset of them plus a `case default:` catch-all (§6.2). `case default:` keeps a `match` well-defined without enumerating every class, and also covers any class the service might add beyond this set in a later revision. A `value` cannot be written in a type annotation (§5.5). Reading a claim is not the only way to get one: the element of a collection mixing scalar value classes is a `value` (`[1, "a"]`, §3.5.9), and so is whatever is read from it — an index, a loop or comprehension variable — and so is an `or` or `and` over two value classes (`1 or "yes"`, §8.2). **A `value` that holds a number.** When every element a mixed collection can hold is an `int`, a `real` or a `bool` — `[1, 2.5]`, `{1, 2.5}`, `[True, 2.5]`, the values of `{"a": 1, "b": 2.5}`, the positions of `(1, 2.5)` — its `value` is known to hold a **number**, and the numeric operators and built-ins take it as one: arithmetic (§8.3), ordering, `==` and membership against numbers (§8.1), `sum` / `min` / `max` (§8.3), `sorted` (§8.6), and `abs`, `round`, `divmod`, `int` and `float` (§8.3, §8.13). Each element keeps its own class, so `[1, 2.5][0]` is `1`, `[1, 2.5][1] + 1` is `3.5` and `min([1, 2.5])` is `1`, as in Python. Its type is still written `value` — in a diagnostic, and wherever it meets a declared type — so it does not satisfy an `int` or `real` parameter, return or annotation; convert it with `int(…)` or `float(…)` (§8.13). An operation over it is typed by the classes that can reach the result. Where the rules of §8.3 give a `real` whichever class the `value` holds — a `real` other operand, `/`, a negative literal exponent — the result is a `real`; otherwise it is again a `value` holding a number, because its class is the element's. So is `sum` over such a collection: `sum([1, 2.5])` is `3.5`, and `sum([x for x in [1, 2.5] if x < 2])` is `1`. The property composes, and is lost wherever a value that is not a number could arrive: - it is kept through reading (`xs[i]`, `for x in xs`, a comprehension, a generator expression, `list` / `set` / `tuple` of the collection), through arithmetic, through `+` of two collections that each hold numbers (`[1, 2.5] + [3]`), and into the keys of a dict, which are matched by value (§3.5.12): `sum({1: "a", 2.5: "b"})` is `3.5`; - where one expression may produce either of two values — a conditional expression, `and` / `or`, the re-binding of a variable, a loop variable left over from a loop, `min` / `max` of separate values or with `default=` — the result holds a number only when both sides do, so `ys = [1, 2.5] if c else ["a", 1]` is a `list` that `sum` refuses; - the inner lists of a nested literal unify the same way, so the lists `[[1, 2.5], ["a", 1]]` holds are not lists of numbers; - a `bool` is the `int` it is, as in Python (§8.1), so `[True, 2]` is a `list` and `[True, 2.5]` a `list` holding numbers: `sum([True, 2.5])` is `3.5`, and `True in [1, 2.5]` is `True`. It keeps its class, so `max([True, 0])` is `True`, the element itself. **A `value` that holds a magnitude.** When every element a mixed collection can hold is a number or a `scientific` — `[scientific("2.0e0"), 1]`, the keys of `{scientific("2.0e0"): "a", 1: "b"}` — its `value` is known to hold a **magnitude**, and it orders as a `scientific` does, by magnitude (§8.1): against a number, a `scientific` or another such `value`, in `sorted`, `min` and `max`, and inside two lists or tuples being ordered. It is not a number, so arithmetic, `sum` and the numeric built-ins refuse it, as they refuse a `scientific`. It composes as a `value` holding a number does, and a number has a magnitude too, so `[1, 2.5] + [scientific("3.0e0")]` holds magnitudes, and `min` of a `scientific` and a number holds one. --- ## 6. Static semantics ### 6.1 Scoping DKE Python is **function-scoped** for variables, as Python is. An assignment `x = ` binds `x` in the enclosing script body, not in the `:`-headed suite that contains it: an assignment inside an `if`, `for`, `match`, `try`, `except`, or `branch` block binds the script's variable, and its value **persists past the block**. A `for` loop body may therefore accumulate into an outer variable, and an `if` branch may conditionally reassign one. A re-assignment MUST keep the variable's type — assigning a value of a different type to a name already in scope is a type_error (§6.4); a same-type re-assignment is permitted. Script parameters are in scope for the entire body. A name is **read** only where every path that reaches the read has bound it — the definite-assignment rule, which the compiler checks, so that no run reads a name unbound. A name first bound inside a block is readable within that block and its nested blocks, and after the statement holding the block when **every path through that statement that completes** binds it: - both arms of an `if`/`else`, and every arm of an `if`/`elif`/…/`else` chain; - every arm of a `match` with an unguarded catch-all (`case _:`, a capture, or `case default:`), and every arm of a `match` over an instance of a class, whose arms must cover every class the instance can hold; - a `try` body, which runs on into its `else`, so the `else` reads what the body bound, and each `except` handler present; a name the `finally` binds is bound on every path; - a `branch` block's body, which runs in full (§9.2.6). A path ending in `return`, `raise`, `break`, or `continue` reaches nothing after the statement, so it need not bind the name. Every path must bind the name to **one** type: paths binding it to two types compile, and a read after them is a type_error naming both. A name bound on some paths only — after an `if` with no `else`, a `match` with no catch-all, or a `try` with a handler that does not bind it — or in a `for` body, which may run zero times, or in the `for`'s `else`, which a `break` skips, is readable within its block only; a read after is a type_error saying it is not bound on every path. Bind it before the statement to read it after. Where CPython raises `NameError` on a run that reaches such a read with the name unbound, DKE Python refuses the read before any run. A `finally` reads none of its `try`'s bindings, since it also runs when the body did not complete. The `except` handler variable is **block-local** and does not leak past its block: it is visible only within its own block, and when the block ends the name is restored to whatever it held beforehand, or becomes unbound if it was not previously bound. The `for` loop variable is not: the loop assigns each element to it in the enclosing scope, so after the loop, and in its `else` body, it holds the last element bound, as in Python, and a loop that runs zero times binds nothing and leaves the name as it was. Being an assignment, a loop over a name that is already a variable keeps that variable's type (§6.3). A comprehension's variable, by contrast, is local to the comprehension (§8.9). `match` case clauses bind no name of their own; the scrutinee's binding (if any) stays visible inside every case body. A class arm's `as` binding is local to its arm (§6.2), while a **value pattern**'s capture and `as` bind as assignments do (§6.2). A `branch` block rolls back the **store** on abort (§9.2.5), never variable bindings: a variable assigned inside a branch keeps its value whether the branch commits or rolls back — variables are not transactional. A path segment that names an in-scope variable (`current(k.s.a)` with `k`, `s` parameters) is resolved to that variable's value; a segment that is not an in-scope name at the point of the call is a literal kind/subject/attribute name. Because this is decided from lexical scope, a variable never captures a same-named literal segment introduced later. A resolved segment MUST be a name — the identifier shape of §3.4. A value that is not (it holds a space, a `.`, leads with a digit, or is empty) raises a `refuse` (§10) naming the value, the variable it arrived in, and which of the kind / subject / attribute positions it filled. Naming the value is what makes the refusal usable where the segments come from data: a loop writing one claim per row runs on a single line, so the line identifies the loop and only the value identifies the row. Being a `refuse` rather than an error is what lets a handler take it per row (§9.3) — see the tutorial's *Loading many facts at once* for the skip-the-bad-rows-and-keep-the-rest shape. A value whose one fault is a combining mark — `"cafe"` followed by U+0301, which renders as `café` — is refused naming the mark by its codepoint, since the mark draws onto the letter before it and nothing on screen shows it. A held value is not read as a written name is (§3.4), so the mark stays and does not compose. Reading the cell and writing it are refused alike. Where the path's leading segment is a declared class, a segment that names BOTH an in-scope variable and a declared field of that class is **rejected at compile time** — the two readings write to different places and the compiler will not guess between them. Rename the variable (or address a field the variable is not named after): ```python class Box: label: string def put(id: string, label: string): remember(Box.id.label, label, "op") # rejected: `label` is both ``` ```python def put(id: string, text: string): remember(Box.id.label, text, "op") # writes Box..label ``` This restricts only the ambiguous spelling, not the capability: a segment holding a variable that is *not* a declared field of the class still resolves to that variable's value, so a computed attribute name remains expressible. A kind with no class declaration has no declared fields, so every segment there keeps the plain in-scope-variable-else-literal reading. The class may be declared by an **imported module** as well as by this one — `M.Class..` and, where the bare name resolves to it ([Python Reference §9.6](/ref/dke/python/#namespacing)), `Class..` are checked against that module's declared fields. Where a name is declared both here and by an import, this unit's own declaration is the one checked, matching how the bare name resolves. ### 6.2 Match exhaustiveness A `match` over a value Python has is Python's: a number, a text, a collection, a `slice`, a view, an iterator or a class instance. Every arm is a test, tried in the order written, and when none holds none runs. What only this language has dispatches by CLASS, and such a `match` MUST cover the case set of the scrutinee's type: | Scrutinee type | Required case set | |----------------|-------------------| | `active_claim` | `{ active_claim, empty }` | | `proof_tree` | `{ proof_tree, empty }` | | `verify_result` | `{ verify_result }` | | `value` (§5.6) | `{ int, bool, string, datetime, duration, real, null, blob, scientific, complex }`, **or** any subset plus `case default:`; none when no arm names a class, or when every arm naming one writes Python's parentheses | | a value Python has | none — no arm is required to take every value, and when none matches, none runs | Any other scrutinee type is a type_error. Over a type with a case set, a missing case is a type_error; a case label not in the set is a type_error; a repeated case label is a type_error, and the order of class labels is not significant. Value patterns are tried in the order written. A class instance the compiler follows to its class also takes the **class match** (§13.5). **Value match.** When the scrutinee has type `value` (§5.6), the case labels are the value classes `int`, `bool`, `string`, `datetime`, `duration`, `real`, `null`, `blob`, `scientific` and `complex`. Like a class match (§13.5), the arms MUST either cover every class or provide a `case default:` catch-all; a value match that is neither exhaustive nor defaulted is a type_error. That holds where an arm writes a label of this language's own, a class named with no parentheses (`case int:`, `case int as n:`, `case null:`). Where every arm naming a class writes Python's parentheses instead, the `match` is Python's, as over a value Python has: every arm is a test, tried in the order written, and none is required. In an arm naming classes, a scrutinee written as a name is read at the class they join to (§5.6), in the arm's guard and body, and in an arm below them at what their tests failing leave it. A `bool` is an `int` (§8.1), so an arm naming `int` takes a `bool` too, as Python's class patterns do, and covers it: the classes are all covered without an arm naming `bool`. An arm naming `bool` below an unguarded arm naming `int` could never run and is a type_error; to tell the two apart, write the `bool` arm first. A **write-producible** class (`int`, `bool`, `string`, `datetime`, `duration`, `real`, `blob`, `scientific`, `complex`) MAY bind the content with a named `case as :` arm — `n : int` inside `case int as n:` — scoped to the arm; the binding is optional (`case int:` with no `as` is well-formed). `case int():`, Python's class-pattern spelling, tests what `case int:` tests for every write-producible class, as `case ():` does for a declared class (§13.5), and `case str():` and `case float():` are the `string` and `real` arms, the names `isinstance` reads for them. In a `match` that is Python's, a type Python has holding one pattern binds the value to it, as Python's does: `case int(n):` binds `n`, and a capture binds it too. Beside a label of this language's own, a type with a pattern inside its parentheses is a type_error, as is a type only this language has holding one; bind the content with `as`. A label that is no class, `null`, `default` or `empty`, takes no parentheses: `case null():` is a parse_error. The one **read-only** class, `null`, is a bare `case null:` arm only: attaching `as ` to it is a type_error, because an absent value has no content to bind. A `case default:` arm likewise MUST NOT carry an `as` binding (it spans more than one class, so no single payload type exists). The bound identifier obeys the reserved-word rule (§6.6) and is visible only within its arm. **Alternation.** A case label MAY name two or more discriminators separated by `|` — `case int | string:` — and the arm is selected when the scrutinee's discriminator is any one of them. An alternation is a spelling, not a widening: it covers exactly the classes it names, and the arms of the `match` must still be exhaustive or defaulted by §6.2's table. `case int | string:` and the two arms `case int:` / `case string:` with the same body are equivalent; which to write is a matter of style. ``` # match-arms.dpy — one arm may name ALTERNATIVES, and a guarded arm may be # followed by a second arm naming the same class. def describe_reading() -> string: remember(Sensor.roomA.count, 42, "demo") c = current(Sensor.roomA.count) match c.value: case int as n if n > 100: return "high" case int as n: return "count " + str(n) case string | datetime: return "not a number" case default: return "other" ``` Four rules constrain it, each a compile-time error: | Written | Refused because | |---------|-----------------| | `case int \| int:` | a discriminator appears twice in one arm; an alternation with a repeat narrows nothing and is a typo | | `case default \| int:` | `default` already covers everything not named above, so OR-ing into it cannot narrow it. Write `case default:` alone | | `case int \| string as n:` | `as` binds the matched content at one class's type, and the alternatives have different types — there is no single type for `n`. Bind in separate arms, or discriminate without binding | | `case int & string:` | there is no AND-pattern. A value has exactly one class, so a conjunction matches nothing | The third rule is the `case default:` restriction above, generalised: a label spanning more than one class has no single payload type, and `default` is that same condition reached by a different spelling. `|` is the alternation separator **between the labels of a case**. In an expression, a guard's included, it is the set union (§8.6), so `case int as n if {n} <= allowed | extra:` reads it there, and on two integers it is the bitwise or (§8.3). **Guards.** A case label MAY carry a condition: `case
, recorded_before=datetime("…"))`  /  `recorded_at_or_before=` / `valid_before=` / `valid_at_or_before=`  /  `forget()` | stmt | (void) | | `assert(K.s.a, src)` | stmt | (void) | | `prefer(K.s.a, src)` | stmt | (void) | | `unprefer(K.s.a)` | stmt | (void) | | `pin(K.s.a)` | stmt | (void) | | `unpin(K.s.a)` | stmt | (void) | | `current(K.s.a)` / `current(K.s.a, datetime("…"))`  /  either with `, known_at=datetime("…")` | expr | `active_claim` | | `get(K.s.a)` / `get(K.s.a, datetime("…"))`  /  either with `, known_at=datetime("…")` | expr | `claim_list` | | `why(K.s.a)` / `why(K.s.a, datetime("…"))`  /  either with `, known_at=datetime("…")` | expr | `proof_tree` | | `why_not(K.s.a)` | expr | `diagnosis` | | `what_needs(K.s.a)` | expr | `diagnosis` | | `caveats(K.s)` | expr | `claim_list` | | `dependents()`  /  `dependents(, transitive=True)` | expr | `claim_list` | | `premises()` | expr | `claim_list` | | `conflicts()` | expr | `claim_list` | | `verify(K.s.a, v)`  /  `verify(K.s.a, v, src)` | expr | `verify_result` | | `agreement(K.s.a, v)` | expr | `int` | | `writers(K.s.a, v)` | expr | `int` | | `would_forget(
, recorded_before=datetime("…"))`  /  `recorded_at_or_before=` / `valid_before=` / `valid_at_or_before=`  /  `would_forget()` | expr | `int` | | `subjects(K.a, v)` | expr | `subject_set` | | `list_subjects(K)` / `list_subjects(K.a)`  /  either with `, datetime("…")`, `, known_at=datetime("…")` or both | expr | `subject_set` | | `list_attributes(K)`  /  `list_attributes(K.s)` / `list_attributes(K.s, datetime("…"))`  /  the two-part forms with `, known_at=datetime("…")` | expr | `string_list` | | `list_values(K.a)` / `list_values(K.a, datetime("…"))`  /  either with `, known_at=datetime("…")` | expr | `claim_list` | | `list_categories()` | expr | `string_list` | | `list_pinned()` | expr | `string_list` | | `create_world(name)` | stmt | (void) | | `merge_world(name)` | stmt | (void) | | `discard_world(name)` | stmt | (void) | | `list_worlds()` | expr | `string_list` | | `compare_worlds(name)` / `compare_worlds(name, name)` | expr | `claim_list` | | `list_result_types()` | expr | `result_type_set` | | `list_scripts()` | expr | `string_list` | 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. They are **soft keywords**, as every verb's word is (§3.7): recognized as a verb only immediately before `(`, so the same spelling remains a usable ordinary identifier elsewhere. **Keyword arguments.** A verb takes the keyword arguments its row above writes, and no other. A keyword argument it does not take is a parse_error at the keyword that names the keywords the verb takes, or says it takes its arguments by position: `current(K.s.a, a=1)` names `known_at=`, and `pin(K.s.a, a=1)` says `pin` takes its arguments by position. A keyword argument the verb takes comes after the arguments it takes by position, and a call writes one. Written before them, it is a parse_error at the keyword that says they come first: `remember(K.s.a, 1, qualifiers={})`, which has no source, is one at column 20. Written after another keyword argument, it is a parse_error at the keyword that says the verb takes one keyword argument in a call: `forget(K.s.a, recorded_before=t, valid_before=u)` is one at column 34. Python reads such a call, so a grammar error later in it, or below it, is the answer instead (§10.1). A positional argument after a keyword argument is a grammar error, as it is in any call (§7): `y = current(K.s.a, known_at=t, b)` is a parse_error at column 33, the `)`. **Arguments by position.** A verb takes as many arguments by position as its row above writes, and a fold (§7.1.1) and the literal factories (§3.5) as many as their forms write. An argument after them is a parse_error at that argument that says how many the call takes: `y = current(K.s.a, t, u)` is one at column 23, "`current` takes 2 positional argument(s) here, and this is one more". A `*` or `**` argument there may spread nothing, and is refused (§10.1). A call short of an argument it takes is a parse_error at its `)` that says how many the call takes: `remember(K.s.a)` is one at column 15, "`remember` takes at least 3 positional argument(s), and this call gives 1". Python reads such a call and leaves its arity to run time, so a grammar error later in it, or below it, is the answer instead (§10.1). `len`, `isinstance` and `issubclass` are built-in functions, and a count one does not take is answered as any built-in function's is (§8.13). **A path argument.** Where a verb's row above writes a path (`K.s.a`, `K.s`, `K.a` or `K`), the path is the whole argument. An argument that goes on past a path as an expression is a parse_error at the argument that says so: `y = current(K.s.a + 1)` is one at column 13, "`current` takes a path here, and this argument goes on past the path as an expression — the path is the whole argument". So is a call in the path's place: `y = current(f(x))` is one at column 13. An argument there that is no path at all is a parse_error at it that says what a path there wants: `y = current("s")` is one at column 13, "expected a path such as K.s.a". An instance written where a path names one of its fields, without the field, is one where the field was wanted. Python reads such a call, so a grammar error in the argument, later in the call, or below it, is the answer instead (§10.1). `branch`, `commit`, and `rollback` are language constructs (§4.3, §9.2.5, §9.2.6), not verbs, and are spelled directly in DKE Python syntax. So is the `world` block (§9.2.9), where `world` is a soft keyword: recognised only immediately after `with`, and an ordinary identifier everywhere else. **Walking a derivation.** `dependents(c)` returns the claims concluded from `c`, one step down, and `premises(c)` the claims `c` was concluded from, one step up: the claims the rule read, or the fields a computed field reads. The operand is a claim bound to a name — read with `current` or `get`, or an element of a `proof_tree` or of `dependents` — or a claim's `claim_id` (§5.2), which names the claim whatever has been written since it was read; an operand that is neither is a type error. A `claim_id` that names no claim the store holds is refused (`refuse`, catchable), as is one that two claims share; a string that is not a `claim_id` is an error. An element of `dependents` is the dependent claim itself, so a chain can be walked a step at a time in either direction. `dependents(c, transitive=True)` goes the whole way down in one read: the claims concluded from `c`, the claims concluded from those, and so on, each listed once, in name order. It answers in full on every store, one where two conclusions each rest on the other included. `transitive=` is a keyword, takes a `bool` and is given at most once; `transitive=False` is the one-step read. Both verbs answer for the store as it stands. A claim someone recorded has no premises. A claim the store no longer holds is refused (`refuse`, catchable) by both verbs, whether it is passed as the claim or as its `claim_id`, as `forget` and `would_forget` refuse it (§10.3). That covers a conclusion withdrawn since it was read, and one a `known_at=` read worked out again, which the store never recorded. An empty answer there could not be told apart from a recorded claim's true lack of premises. A conclusion whose validity window does not cover the present still names its premises: they are read at a time inside its window. Where two rules conclude different values at one cell, each claim answers its own premises. **As-of reads.** `current`, `get`, `why`, `list_subjects`, `list_values` and `list_attributes(K.s)` accept an optional `datetime` (§3.5.5) final argument naming the time the read is answered **as of**. `current(K.s.a, datetime("…"))` yields the single value active then, `get(K.s.a, datetime("…"))` the claims valid then, and `why(K.s.a, datetime("…"))` the derivation as it stood then — the premises that were in force at that instant, which is not always the derivation that holds now and is sometimes a derivation where none holds now at all; the three enumerations list what a traversal would find at that time — `list_subjects(K, datetime("…"))` the subjects of the kind, `list_values(K.a, datetime("…"))` the values at the attribute, `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 (below) is in play. A fact carried without a window is valid at all times, so its as-of value is its present value. The time may be **future as well as past**. A scheduled commitment written with a window that has not yet opened is not part of the present, so nothing at the present reports it — the anchor is how it is read back, and on the enumerations it is how it is found at all. **Reads as of what the store knew.** The same six reads accept one keyword argument, `known_at=`, a `datetime` naming a moment on the other time axis: not when a fact was true of the world, but what the store had been told by then. `get(K.s.a, known_at=datetime("…"))` returns the claims the store held at that moment. A claim written later is not among them, and a claim later revised or replaced 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 does over a disagreement now. `why` and the three enumerations answer the same way. The positional time and `known_at=` 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. A read without `known_at=` answers over everything the store holds now, and one whose `known_at=` is later than every write answers the same. `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, including a conclusion withdrawn since and leaving 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. The read refuses, catchably, in three cases (§10.3). The answer rests on a rule that was changed after that moment, or on one whose text at that moment the store holds no record of, so its text then is not known; the reason is `derived_field`, and the remedy is to read without `known_at=` or as of a moment after the change. A rule whose earlier text the store holds no record of takes part once it is compiled again, unchanged, for a read as of any moment from then on. Or 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; the reason is `not_recorded`. Or the answer rests on more of the store than a read as of an earlier moment may work over; the reason is `limit_exceeded`, and the same read over the same data refuses the same way every time. **A read that finds nothing says why, when the reason is an import.** A read naming a kind the store does not know refuses (§10.3). Where a missing `import` is the reason, the refusal says so instead, in the two cases one produces. This holds for `current`, `get`, `subjects` and the `list_*` reads, and for `forget`, which a missing import would otherwise turn into a removal of nothing that reports success: - the name is itself a stored module (`list_attributes(catalog.Widget)` with no `import catalog` reads `catalog` as a kind and `Widget` as a subject); - some stored module declares a class of the name asked for (`list_attributes(Widget)` reads a global kind `Widget`, which does not exist, while `catalog` declares one). Both name the module and the `import` that reaches it. Both are gated on the kind being **absent**, so a read of a kind that exists still answers `empty`, or a collection with no elements, where that kind holds nothing; and a name no module declares refuses as a kind the store does not know (§10.3), naming no import. **Kind names in a listing are addressable.** `list_categories()` names a kind a module declares in the qualified form `.` — the form §7.1's verbs take, given an `import` of that module ([Python Reference §9.6](/ref/dke/python/#namespacing)). A kind no module declares carries no qualifier and is named as written. A name a listing returns is always a name the language accepts. The same holds for an address: `list_pinned()` names each pinned cell in the form `pin` and `unpin` take, so an address it returns, written back into `unpin`, lifts that pin. `list_attributes(K)` and `list_categories()` take no as-of argument, because neither answers about an instant. The first gives the attribute names recorded for a kind and 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. Note that `list_attributes` therefore 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 one where it is not accepted is a compile error naming the form that takes it. **Validity windows.** `remember` and `update` accept an optional pair of trailing `datetime` (§3.5.5) bounds — a **from** time and a **to** time — naming the window during which the written fact is valid: `remember(K.s.a, v, src, datetime("2019-01-01T00:00:00Z"), datetime("2021-01-01T00:00:00Z"))`. Either bound may be the literal `null`, leaving that side **open** — `datetime("2024-…"), null` is valid from 2024 onward with no end, and `null, datetime("2024-…")` up to 2024 with no start; at least one bound must be given. Omitting the pair records an **always-valid** fact (the default). Both bounds are inclusive. A windowed fact is selected by an as-of read whose time falls inside its window (above), so two writes at one cell with non-overlapping windows model a value that changed over time, whether one source writes both or two sources write one each. Where a window overlaps one the same source already gave at the cell, `remember` refuses a second value, and refuses the same value too unless the window is the same: moving a window is a revision, which is `update`'s. `update` supersedes only that source's claims whose windows overlap the new one (§7.5). **Qualifiers.** `remember` and `update` accept one keyword argument, `qualifiers=`: a dict (§3.5.12) from name to value stating facts the writer wants the claim to carry about itself — `remember(K.s.a, v, src, qualifiers={"method": "observed", "confidence": 0.8})`. It follows every positional argument, the window included, and is keyword-only because the positions after the source are the window's. Each name is a name in the sense a path segment is — it starts with a letter or `_` and continues with letters, digits, `_` or `-` — and each value is one value a claim could hold: a `string`, `int`, `bool`, `real`, `datetime`, `duration`, `blob`, `scientific` or `complex`. A claim carries at most 16. An empty dict writes none. They are read back through the claim's `qualifiers` field (§5.2). A claim's qualifiers are written with it and stay with it. The same source restating the same value with the same qualifiers is an ordinary no-op restatement; restating it with **different** qualifiers is refused as a conflicting write (§10.3), exactly as restating a different value is, and `update` is how a source revises its claim — value, qualifiers or both. **Forget is hard, and every form names what it takes.** `forget` takes a path — `K.s.a` (one cell) or `K.s` (a whole subject) — or that path plus the value and source that name one claim, `forget(K.s.a, v, src)`. Both `v` and `src` are required in the claim-scoped form, because a value alone does not name a claim: two sources may record the same value at one cell. `v` MAY be of any type a value can be written with — `string`, `int`, `bool`, `real`, `datetime`, `duration`, `blob`, `scientific` or `complex` — and it is matched **by value and by type together**. A cell may hold one text in two types, so `5` and `"5"` name different claims and a literal of the wrong type names none: the call is a refuse, and the refusal states the type recorded there. Type equality in this form is EXACT. §8.1 makes `int` and `real` one numeric family for comparison, and that widening does NOT extend here: a removal names a specific claim and takes it permanently, so an `int` literal never reaches a stored `real`. A claim can also be named by the claim itself: `forget(c)` removes the one claim `c` and leaves every other claim at its cell standing. The operand is the one `dependents` takes — a claim bound to a name, read with `current` or `get` or bound as an element of what they return, or a claim's `claim_id` (§5.2). It tells apart the two claims the value-and-source form cannot: one source's value recorded for two periods is two claims with one value and one source, and `forget(K.s.a, v, src)` removes the first of them, while `get(K.s.a, datetime("…"))` at an instant inside the second period returns the claim that `forget` then removes alone. The claim is looked for in the store as it stands. A claim removed since it was read, or written inside a `branch` that rolled back, is refused (`refuse`, catchable), as is a `claim_id` two claims share, and nothing is removed; a superseded claim is still recorded and is removed like any other. The claim is the whole address, so nothing follows it, and a claim's field in the place of a path, `forget(c.claim_id)`, is a compile error naming `forget(c)`. A fourth form is **time-scoped**: `forget(K.s.a, datetime("…"))` removes the values at that cell whose validity window (§7.1) covers the instant named, and leaves every other value there standing. It is the removal counterpart of an as-of read — the same instant that would tell you what was in force selects what to remove. The time-scoped form takes a `K.s.a` path only: an instant narrows which values AT A CELL are named, and a subject holds many cells with many windows rather than one window to narrow. Offering a time on a `K.s` path is a compile error naming the two forms that would have worked. The two-argument forms are told apart by the **type** of the second argument, not by counting: a `datetime` is the time-scoped form, anything else is the start of the claim-scoped one and its source is still required. A fifth form is **range-scoped** and is spelled with a keyword argument: `forget(K.s.a, recorded_before=datetime("…"))` removes the older versions before the instant, and **never the value in force**. It is the only removal form that cannot empty what it names, and that guarantee is the reason it is a separate address rather than an option on the others. It is also the only form that takes more than a cell. Three widths — `K.s.a` (one cell), `K.s` (every cell of one subject), `K` (every cell of the kind) — and the safety property is the same predicate at each, so a kind-wide range cannot empty anything. `K` is an address ONLY with a range: a bare `forget(K)` is refused at compile time. Four keywords choose an axis and a boundary: `recorded_before=` / `recorded_at_or_before=` select by when a version was **written**, `valid_before=` / `valid_at_or_before=` by when it stopped **being true**. A version carrying no closed validity window is not named by any `valid_*` range, so the two axes may report different counts over the same cell; that is the axes disagreeing, not an error. There is no `after` spelling: it would name the versions newer than the instant, reaching the value in force, and removing a current value is what the first four forms are for. The range form is keyword-only. A positional instant already means the time-scoped form above, the two select disjoint sets of versions, and a call that could be read as either would have no diagnostic available to catch it. `would_forget(K.s.a, recorded_before=datetime("…"))` returns, as an `int`, how many versions the matching range would remove — removing none of them, recording nothing, and requiring only a read grant. It takes the same three widths as the range removal, so every removal that can be written has a preview that can be written, and both read one selection rather than agreeing by convention. With an address the bound is required; there is no bare form. `would_forget(c)` previews `forget(c)`: it returns `1` while the store holds the claim, and refuses as `forget(c)` would when it does not. Removal is **irreversible**. `forget(K.s.a)` permanently removes every value ever recorded at the cell, not just the current one; `forget(K.s)` removes the whole subject, after which its kind is gone; `forget(K.s.a, v, src)` removes exactly the claim it names and leaves every other claim at that cell standing, as `forget(c)` removes the claim `c`; `forget(K.s.a, datetime("…"))` removes the values in force at that instant and leaves the rest. Nothing is retained in history that a read can reach, and nothing in this language reinstates what it removed. Removal means what it says. One qualifier, and it is not part of this language: while a checkpoint stands, a removed value stays recoverable outside every read, so the `restore` tool of the MCP Reference can bring it back: a value that was in force as a new claim at the current instant, and an older version into the history where it stood. That is a re-assertion and not a rewind — the removal still happened and the claim was still reclaimed. With no checkpoint standing at the moment of removal, nothing is kept. **Removal cascades; it does not refuse.** Forgetting a fact that other facts were derived from succeeds, and those derived facts go with it. Derived facts are conclusions the engine can rebuild, not data you wrote — so a conclusion that some other standing rule still supports is re-derived immediately and stays. The practical guarantee is that a removal always completes: "removed" never means "removed unless something pointed at it." **Removal needs the delete grant.** A key scoped `read-write` can add and revise but cannot remove; removal requires a `read-write-delete` key. A key's scope is fixed when the key is issued and is not something a program can change, so a `forget` that exceeds it is refused before any statement runs. Compiling a module the store holds removes what an earlier text installed and this one no longer declares — a `def`, a method, a rule, a computed field or a constraint ([Python Reference §9.5](/ref/dke/python/#re-submitting-reconciles)) — and that is a removal too. Under a `read-write` key such a compile is refused with `scope_forbidden` before any statement runs: the answer names what would go, and the store is left as it was. A compile that removes nothing, changes a declaration in place or drops only the module's body is an ordinary write. **Adding without revising takes the read-add grant.** A key scoped `read-add` reads, and writes only what adds to the store: a value where none is in force, a restatement of a value its source holds, a value from a source that holds none there, and an `assert` where nothing is recorded. An `update` over a value its own source holds is a revision, whether it changes the value, the window or the qualifiers, and it is refused when it runs, with `scope_forbidden`. The program ends at that statement, `try` does not catch the refusal, and what the program wrote before it is kept. Committing a branch is refused in the same way when the commit would replace a value the store holds, and the branch stays open. What a standing rule concludes from an added fact changes as it does under any key. A `read-add` key cannot remove, cannot `pin`, `unpin`, `prefer` or `unprefer`, cannot restore a checkpoint or drop one, and cannot compile over a module the store holds; each of these is refused before any statement runs. **Absence-assertion verb carries no value.** `assert(K.s.a, src)` asserts that the value at `K.s.a` is known to be absent per `src` — an assertion observably distinct from there being no claim at all. **Script data in a program.** `list_scripts()` executes from inside a program and returns the stored script names as a `string_list`. Forgetting a script and reading its metadata are the wire's `forget` and `info` tools ([MCP Reference §9.2](/ref/dke/mcp/#scripts-address-by-module.script)), not statements of a program body. The service stores no source text, so there is no source-readback verb. #### 7.1.1 Aggregate query functions An **aggregate query** computes a summary value over a whole attribute of stored data — the values of one attribute across every subject of a kind. It is a **read-only** expression: it never changes the store. Three call shapes, by the function's arity: | Shape | Call form | Meaning | |-------|-----------|---------| | single-attribute | `fn(K.attr)` | fold `K.attr` over all subjects of `K` | | ranked | `percentile(K.attr, )` | the ``-th percentile (rank `0`–`100`) of `K.attr` | | two-attribute | `fn(K.x, K.y)` | fold the paired `(K.x, K.y)` values (same kind `K`) | The available folds: | Family | Functions | |--------|-----------| | totals | `count`, `sum`, `product` | | central tendency | `avg`, `median`, `midpoint`, `mode`, `geomean`, `harmmean`, `rms` | | spread | `min`, `max`, `span`, `variance`, `stdev`, `iqr`, `mad`, `cv` | | shape | `skewness`, `kurtosis`, `entropy` | | identity / positional | `distinct`, `argmin`, `argmax`, `first`, `last` | | ranked | `percentile` | | two-attribute (association / regression) | `covariance`, `correlation`, `slope`, `intercept`, `rsquared` | The **identity / positional** folds answer *"which"* and *"when-ordered"*, where the others answer *"how much"*: `distinct(K.attr)` is the number of **distinct values** in the attribute (where `count` counts the values THEMSELVES, so a location holding two values in force contributes two — §7.6); `argmin(K.attr)` / `argmax(K.attr)` return the **subject** whose value is smallest / largest (the identity, where `min`/`max` return the value); `first(K.attr)` / `last(K.attr)` return the **earliest- / latest-recorded** current value (by when it was recorded, not by magnitude). When several subjects tie for the extreme, `argmin`/`argmax` return the first subject in name order. **Equal values are one value.** `distinct`, `mode` and `entropy` count values the way a Python `set` holds them: numbers equal in value are one value whatever their type, so `1`, `1.0` and `True` count once, while the string `"42"` and the int `42` are two values. Over an attribute holding `1`, `1.0`, `2` and `2`, `distinct` yields `2`, as `len({1, 1.0, 2, 2})` does. `mode` yields the most frequent value, and among equally frequent values the smallest (Python's `statistics.mode` takes the one seen first). The value it yields is the one recorded first of those equal to it, so over `1.0` then `1` `mode` yields `1.0`. The two-attribute folds take two attributes of **one** kind (`slope(Point.x, Point.y)`); both attributes are read over the same subjects. `K`, `attr`, `x`, `y` follow the ordinary slot-grammar rules (§7.2) — each may be a literal name or a `string` parameter/variable resolved at runtime. A second operand that goes on past an attribute or a name, or is a string or a list, is a parse_error at the fold's name that says it must be a `K.attr` path: `y = covariance(K.x, x + 1)` is one at column 5. Python reads such a call, so a grammar error inside the operand, later in the call, or below it, is the answer instead (§10.1). The rank of `percentile` is an `int` from `0` to `100`, written as an integer literal or as the name of an `int` parameter or variable, which is resolved at runtime as the names beside it are. A literal outside the range is a compile error, and a name holding one is a runtime `refuse` (§10.3). An expression is bound to a name first: `r = p * 10`, then `percentile(K.attr, r)`. A single-attribute fold takes nothing after its attribute. Whatever is written there is a parse_error at the fold's name that gives the fold's form, with one attribute: `y = avg(K.c, 1)` and `y = avg(K.c, x + 1)` are each one at column 5. A third argument to a ranked or two-attribute fold is one more than it takes (§7.1). An aggregate query takes no keyword argument. One written is a parse_error at the keyword: `y = avg(K.c, key=1)` is one at column 14. `key=` and `default=` on `min` and `max`, and `start=` on `sum`, are each refused as what the same name takes over a list (§8.3). Python reads such a call, so a grammar error later in it, or below it, is the answer instead (§10.1). **Result type.** An aggregate query returns a `value` (§5.1). Its runtime class follows the attribute(s): a numeric attribute yields a number (`int` or `real`); over a `datetime` or `duration` attribute, a fold yields a `datetime` or `duration` as appropriate. A `bool` attribute is numeric, since a `bool` is the `int` it is (§8.1). A fold that computes a value yields a number, so over `True`, `True` and `False` `sum` yields `2` and `avg` `0.6666666666666666`. `min`, `max`, `mode`, `first` and `last` select a stored value and yield it as it was stored, so `max` over the same attribute yields `True`. Among equal values a selection yields the one recorded first, and `last` the one recorded last: over an `int` attribute holding `True` and then `1`, `max` yields `True` and `last` yields `1`, as `max` and `[-1]` answer over the list `[True, 1]` in Python. Over values holding a `real`, a fold that computes a value yields a `real`, whole or not: over `0.5` and `1.5` `sum` yields `2.0` and `median` `1.0`, and over `1` and `1.0` `sum` yields `2.0`, as Python's `sum` and `statistics.median` do. Over `int` and `bool` values alone, `sum`, `avg`, `span`, `midpoint`, `variance` and `product` yield an `int` when the result is whole and a `real` otherwise, so over `1` and `3` `avg` yields `2`, as `statistics.mean` does. `stdev`, `iqr`, `percentile`, `mad`, `cv`, `geomean`, `harmmean`, `rms`, `skewness`, `kurtosis`, the two-attribute folds, and `median` over an even count yield a `real`, whole or not: over `1` and `3` `stdev` yields `1.0`, as `statistics.pstdev` does, and `median` yields `2.0`, as `statistics.median` does. Over an odd count of `int` values `median` yields the middle value, as `statistics.median` does. It is computed, not selected, so over an odd count of `bool` values it yields `1` or `0`, and over `1.0`, `2` and `3` it yields `2.0`, where Python's `statistics.median` yields the middle element. Three folds do not follow the attribute: `distinct` always yields an `int` (a count), and `argmin`/`argmax` always yield a `string` (the subject), regardless of the attribute's class; `first`/`last` follow the attribute like the value folds. Because the class is not fixed at compile time, bind the result to a name and destructure it with `match` (§9.2.3) to consume a specific class, or use it directly wherever a `value` is expressible (`print`, `+`, `==`). **Units, for a fold over a `datetime` or `duration` attribute.** Most folds return a `duration` (or `datetime`), which carries its own units — `stdev` over durations reads back as `PT1H30M`, and the question does not arise. Four folds instead return a **plain number** whose magnitude depends on a unit that has to be chosen: `variance` (a squared span), `covariance` (a span times the other attribute's unit), `slope` where the *first* attribute is temporal (a rate per unit of time), and `product`. **For these, the unit is the SECOND.** So a variance over durations of one and two minutes is `900` (seconds squared), and a slope against a `datetime` is a change per second. This is worth stating because nothing about the value itself reveals which unit was chosen, and a statistic read in the wrong unit is wrong by a large factor rather than visibly broken. **Definitions, where a fold name admits more than one.** Some of these names denote a different number in different systems, and for the same reason the paragraph above gives — the value does not reveal which was chosen, and the alternative is a plausible number rather than a visibly broken one — this section states which: | Fold | Definition | |------|------------| | `variance`, `stdev` | the **population** figures (divide by `n`), not the sample ones (`n-1`) | | `skewness` | the **population** moment coefficient `g1`, not the sample-adjusted `G1` | | `kurtosis` | **excess** kurtosis: `0` for a normal distribution, not the raw fourth moment | | `mad` | the **median** absolute deviation about the median, not the mean absolute deviation | | `entropy` | Shannon entropy in **nats** (natural log), not bits | | `percentile`, `iqr` | **linear interpolation** between the two nearest ranks, not a nearest-rank or exclusive method, computed exactly and rounded once; `iqr` is that method's Q3 − Q1, one exact difference rounded once | | `covariance` | the **population** covariance (divide by `n`), where Python's `statistics.covariance` is the sample one (`n-1`) | `stdev` is the one a Python reader should read twice. In CPython's `statistics` module `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 language answers `2.0` and `statistics.stdev` answers `2.138090`. Both are correct for their own definition, and only this sentence tells you which you have. **Rounding, over `real` values.** A fold has no order to add its values in: not the order they were recorded in, and not any order a program can see. So it does not add them the way a loop over a list does. `sum` yields the **exactly rounded** sum: the exact sum rounded once, which is the value Python's `math.fsum` gives over the same `real`s, and an `int` enters as itself, where `math.fsum` converts it to a `real` first. `avg` yields the exact mean rounded once, and `rms` the exact root mean square rounded once, every square exact. Python's `statistics.fmean` rounds the sum before it divides, and `math.sqrt(statistics.fmean([x * x for x in xs]))` rounds each square, the mean and the root, so either can differ in the last place: over `-6.875469`, `88.671343`, `29.794911`, `80.180098`, `-77.358807`, `-6.18619` and `-50.685433`, `avg` yields `8.220064714285712`, where `statistics.fmean` gives `8.220064714285714`. `product`, `geomean` and `harmmean` yield their exact values rounded once too, in no order, every `int` entering as itself: over `1e-160`, `1e-160`, `1e160` and `1e160`, `product` yields `1.0`, where `math.prod` over them in that order gives `0.9999888671826831`, and over ten values of `0.1`, `geomean` yields `0.1`, where `statistics.geometric_mean` gives `0.10000000000000002`. So over ten values of `0.1`, `sum` yields `1.0` and `avg` yields `0.1`, and `sum` over the list `[0.1] * 10` yields `1.0` too (§8.3). `min` and `max` select a member, and `median`, `midpoint` and `span` select one or combine two, exactly and rounded once, every `int` entering as itself. `percentile`, `iqr` and `mad` yield their exact values rounded once too: neither the rank nor the interpolation between two members rounds, `iqr` is one difference of two exact quantiles, and every deviation `mad` takes is exact, where the same formulas computed in `real`s round at each step, and over a column near `10⁸` an `iqr` near `1` can be wrong from its eighth digit. `variance`, `stdev`, `cv`, `skewness`, `kurtosis` and the two-attribute folds yield their exact values rounded once as well, so `variance` and `stdev` yield what `statistics.pvariance` and `statistics.pstdev` give, and over three values of `0.1` each of `variance`, `stdev`, `skewness` and `kurtosis` yields `0.0`. `entropy` yields its exact value rounded once too, where a Python computation over a list rounds each logarithm, quotient and addition: over six values held once, once, twice, five, three and five times, `entropy` yields `1.6110656763229676`, where `-sum(c / n * math.log(c / n) for c in counts)` gives `1.6110656763229678`. Where a fold's formula, computed as Python computes it, passes the largest `real` on the way though 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 `OverflowError`, and over `1e300`, `1e300` and `3e300`, `rms` yields `1.9148542155126762e+300`, where Python's formula for it answers `inf`. A fold whose value is itself past the largest `real` yields `inf` or `-inf`, as `variance` over those three values does. **Position.** An aggregate query reads the service, so — like `current` and the other reads — it is used in **expression position bound to a name on its own line** (`m = avg(Reading.value)`), then referenced, or as the **whole value of a `return`** (`return avg(Reading.value)`), in a value script and a value method alike; it may not be written as a bare argument to another call. An aggregate over an attribute with no data is a runtime refuse (§10.3). A fold that **compares by value** — `sum`, `avg`, `min`, `max`, `stdev`, `argmin`, `argmax`, and the rest — over an attribute whose values are **not numbers** (a text attribute, or one mixing numbers with non-numeric values) is likewise a runtime refuse: the fold declines rather than treating a non-number as zero. So is one over an attribute holding values of two kinds among the numbers, `duration`, `datetime` and `scientific` — a `duration` beside an `int`, say — which Python's `sum` and `max` refuse with a `TypeError`. The folds that do **not** 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 this ground. (A `complex` has no order, so the folds that compare by value refuse an attribute holding one, `sum` among them.) An attribute mixing those classes folds under them too, and `mode`, `first` and `last` return the value they chose 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. The fold names are **soft keywords**: recognized as an aggregate only immediately before `(`, so every name remains a usable ordinary identifier elsewhere. #### 7.1.2 Worlds A **world** is a named alternative to the store that is 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 into a world and reads it inside a `world` block (§9.2.9). A later run can enter the same world again, so one world can be built up, read and compared over as many runs as it takes. | Call form | Position | What it does | |-----------|----------|--------------| | `create_world(name)` | stmt | creates the world `name`, holding nothing of its own yet | | `list_worlds()` | expr → `string_list` | the worlds that exist, in name order | | `compare_worlds(a)` | expr → `claim_list` | the claims world `a` holds that the store does not hold | | `compare_worlds(a, b)` | expr → `claim_list` | the claims world `a` holds that world `b` does not hold | | `merge_world(name)` | stmt | makes the world's writes the store's own, and removes the world | | `discard_world(name)` | stmt | removes the world and everything written in it | Each name is a `string` expression. A world's name follows the rule a branch's name does (§9.2.5), and a world never shares a name with an open branch. **Comparing.** A claim of `a` is left out of `compare_worlds(a, b)` when `b` 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 conclusion is compared like any other claim, so where two worlds' facts differ, the conclusions drawn from them differ too, and both are listed. Everything the store holds is held in every world, so `compare_worlds(a)` is what `a` adds to the store. Asking in both directions answers every difference, and a field named on both sides is one where the two worlds disagree. The claims come in name order, and each is an `active_claim` carrying the fields every claim does (§5.2). **Keeping a world current.** What the store gains reaches a world when the world is entered, compared or merged: 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. **Merging.** `merge_world(a)` makes `a`'s writes the store's own. Where the store and `a` 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, and each keeps, as its `.created_at`, the time it was written in the world. After a merge or a discard the world no longer exists. A merge that would put a value written in `a` at a field one of the store's rules concludes, where the rule does not yield to a recorded value, is a `refuse` (`asserted_field`) naming the value and the rule; nothing is merged, and `a` keeps everything written in it. **What a world cannot change.** A world adds to the store and never changes what the store held before it. Inside a world, forgetting a claim or a rule the store holds is refused, and so is `prefer` or `unprefer` at a field the store has judged, exactly as inside a branch. A conclusion the store drew from a claim the world revises with `update` does not hold inside the world, as it would not outside once revised (§7.5); the store keeps it, and reads it again outside the world. The same holds of a conclusion the store drew from the absence of a fact the world writes: outside, that fact would withdraw it. To see the store with one of its facts taken away, use `suppose_absent` (§15.1), which answers inside a world as it does outside. **Where the verbs are reached.** `create_world`, `merge_world`, `discard_world` and `compare_worlds` are refused inside a `world` block or a `branch` block; `list_worlds` answers anywhere. Each of these is a `refuse`, catchable, as are: creating a world that exists, or one whose name an open branch holds; entering, merging, discarding or comparing a world that does not exist; and a name that is not one identifier (§9.2.5). **Which keys may.** Creating or merging a world, and writing inside one, need a key that can write. Discarding a world removes what was written in it, so it needs the delete grant, as `forget` does. Reading inside a `world` block, `compare_worlds` and `list_worlds` need only a key that can read. A world keeps what is written in it until it is merged or discarded. ### 7.2 Slot validity Slot identifiers (kind, subject, attribute) are strings under §3.4 (UAX #31); a source is a label rather than an identifier (§7.3). At runtime, an implementation MAY refuse a slot containing characters not in the target service's identifier alphabet; this is a refuse, not a type_error. Values (the `v` in `remember`, `update`, `verify`, and `subjects`) are **scalar literals** — a `string`, an `int`, or a `bool` — and are NOT identifier-shaped; the two **write verbs** (`remember`, `update`) additionally accept a `datetime` literal (§3.5.5), a `duration` literal (§3.5.6), a `real` literal (§3.5.7), a `blob` literal (§3.5.8), a `scientific` literal (§3.5.14), or a `complex` (§3.5.15). The value slot is type-checked against these types; a compound value (a `claim_list`, a path, and so on) is a type_error. - A **write verb** (`remember`, `update`) records the value **with its literal type**: `remember(K.s.a, 42, src)` stores the `int` `42`, `remember(K.s.a, True, src)` stores the `bool` `True`, `remember(K.s.a, "42", src)` stores the text `"42"`, `remember(K.s.a, datetime("2026-01-02T03:04:05Z"), src)` stores a `datetime`, `remember(K.s.a, duration("PT1H30M"), src)` stores a `duration`, `remember(K.s.a, 3.5, src)` stores a `real`, `remember(K.s.a, blob("48656C6C6F"), src)` stores a `blob`, and `remember(K.s.a, scientific("1.5e3"), src)` stores a `scientific`, and `remember(K.s.a, 3 + 4j, src)` stores a `complex`. The recorded type is a property of the claim and reads back through `.value` (§5.6). - The **query verbs** (`verify`, `agreement`, `writers`, `subjects`) compare **type-discriminatingly**: the query literal's type is part of the match, so `subjects(K.a, 42)` selects the subjects whose value at `K.a` is the `int` `42` and does **not** select a subject carrying the text `"42"`; `subjects(K.a, "42")` selects text `"42"` only. `verify(K.s.a, v)` reports a match only when the stored value and its type agree with `v`. This is the same equality `==` follows (§8.1), applied at the query boundary: a class mismatch compares unequal rather than coercing, and no value is widened or narrowed to fit — **with §8.1's one numeric exception, which applies here too.** `int`, `real` and `bool` are one numeric family, so `subjects(K.a, 5)` selects a subject whose value is the `real` `5.0` as well as one whose value is the `int` `5`, `verify(K.s.a, 5)` matches a stored `5.0`, and `subjects(K.a, 1)` selects a subject holding `True`, as `True == 1`. It is the number that is compared, never its spelling — so a stored `5.0` matches the literal `5` while a stored `5.5` does not, and a matched claim still reads back at the type it was written (`.value` on that claim is `5.0`, not `5`). Everything else stays discriminating — the text `"5"` is a different class and does not match. This is what makes a query verb and a rule premise answer "which subjects have `K.a == 5`" the same way (§14.2). `datetime`, `duration`, `real`, `blob`, `scientific` and `complex` are **not** query-slot types: such a literal in `verify` / `agreement` / `writers` / `subjects` is a type_error. A `real`-valued claim is therefore reached with the equal `int` literal where one exists (`subjects(K.a, 5)` finds `5.0`); where none does (`5.5`), read the claim back and match its `value` (§5.6). - `agreement(K.s.a, v)` returns, as an `int`, **how many distinct sources recorded `v` at `K.s.a`** (type-aware, as above). It is a count of corroboration, **not a judgment of truth**: a value recorded by many sources is not thereby correct, and the language does **not** assert those sources are independent — it reports only how many recorded the value and never ranks or certifies them. Which sources to trust is the caller's judgment; read them by name with `get` (§7.1). `agreement` counts every source, so it takes **no** source argument (one is a type_error). A cell that never held `v` reports `0`. - `writers(K.s.a, v)` returns, as an `int`, **how many distinct store keys wrote `v` at `K.s.a`** (type-aware, as above). It is the companion of `agreement` on the other provenance axis, and the two answer genuinely different questions: `agreement` counts what was **cited**, `writers` counts who **wrote**. Three sources cited by one key is corroboration a single party assembled; one source written by three keys is three parties independently recording the same thing. Reading either as the other is the mistake this pair exists to prevent. Like `agreement` it is a count, never a judgment, and it takes **no** source argument. A claim written outside an authenticated call has no key and is not counted — so `writers` may be lower than `agreement` at the same cell, and a cell whose claims were all written unauthenticated reports `0`. A path whose segment count does not match the verb's slot shape is a runtime refuse with a teaching message. ### 7.3 Source argument The source attribution of the write verbs (`remember`, `update`), of `assert`, and of the optional third argument of `verify` is the last call argument and MUST be a `string`. A source is a **label**, not an identifier: any non-empty text naming where the content came from — a citation (`"2026 finance act"`), a register, a URL. It is stored and read back exactly as written, with no normalization, so two texts that differ in any character are two sources: `"2026 finance act"` and `"2026-finance-act"` are not one, and `agreement` counts them twice. A source MUST NOT be empty, and MUST NOT hold one of the few characters the implementation reserves. A **literal** source that breaks either rule is a type_error. One that arrives at run time — from a parameter, a row, a computed expression — is a **refuse** (§10.3), so a loader can catch it per row and keep the rest. Either refusal names its fault; a reserved character is named by its code point (`U+XXXX`) and byte, never echoed back. ### 7.4 Statement-position vs expression-position A verb listed as **stmt** in §7.1 MUST appear only as a statement (not bound by an assignment, not embedded in a larger expression). Binding a stmt-position verb is a type_error (§6.4). A verb listed as **expr** produces a value of the named result type and may bind an assignment, be a `match` scrutinee (if a discriminator), be iterated (if a finite collection), or be composed with operators per §8. ### 7.5 Write-verb semantics - **Writes are permissive.** `update(K.s.a, v, src)` on a location with no active claim succeeds and establishes the claim — it does not require a prior `remember`. - **`update` revises the writing source's own claim.** `update(K.s.a, v, src)` supersedes the claims `src` holds at `K.s.a` whose windows overlap the new one, and no other claim. A value another source holds there stands beside the new one, so a revision never settles a disagreement (§7.6); to replace another source's value rather than stand beside it, remove that claim with `forget(K.s.a, v, src)`. A superseded claim stays readable through `get` and `why`. Restating exactly what `src` holds there — the same value, the same qualifiers and the same window — writes nothing. The same value over a different window that overlaps the old one is a revision, and the new window is the one in force. A claim `src` holds in a window the new one does not overlap stands, so one source records a value that changed over time by writing each period with `update`. - **`update` inside a `branch` or `world` block** revises a claim `src` recorded before the block as it would outside one. Inside the block that claim no longer reads, at any instant, and a later `remember` there is judged against the new claim. The earlier claim itself is not changed: a rollback, or leaving a world, leaves the store as it was (§9.2.5, §9.2.9), and a commit or a merge leaves it as the same `update` written outside the block would. Of two such claims the later stands, in the block as after the commit or the merge: where the store is revised after a world wrote, the store's revision reads in the world in place of the world's own. What the store's rules concluded from the earlier claim, and anything concluded from that in turn, no longer holds inside the block either, as after the commit or the merge; the store keeps it. A conclusion the rules still reach from what the block reads holds there as it would outside. - **A fact written inside a `branch` or `world` block** where the store's rules concluded something from that fact's absence: the conclusion, and anything concluded from it in turn, no longer holds inside the block, as after the commit or the merge. The store keeps it, so a rollback, or leaving a world, reads it again (§9.2.5, §9.2.9). - **`pin` protects.** At runtime, `forget(K.s.a)` at a pinned location refuses, and so does `forget(K.s)` for the subject above it and every removal of one claim there, `forget(c)` included, until `unpin(K.s.a)` lifts the pin (§10.3). - **`pin`, `unpin`, `prefer` and `unprefer` never change nothing.** Each refuses a call that names something the store does not hold — `pin` or `prefer` at a location with no value, `prefer` naming a source that recorded no value there, `unpin` or `unprefer` where there is neither the mark it lifts nor a value — with the reason `not_recorded`. Each refuses a call asking for a state that already holds — a location already pinned, or not pinned, a source already preferred, no preference standing there — with the reason `already_in_effect`. A program making sure of a state catches that one and carries on. `unpin` and `unprefer` lift any pin or preference that stands, whatever the location holds now. - **Writes are durable as executed.** A later statement's failure does not undo earlier writes; transactional grouping is the `branch` construct's job (§9.2.5). - **`forget` removes what you address.** `forget(K.s.a)` removes the whole cell and `forget(K.s)` the whole subject, as above. `forget(K.s.a, v, src)` removes **one claim** — the one recording value `v` from source `src` — and leaves every other claim at that cell untouched. The value and the source are **both** required: a value alone does not name a claim, since two sources may record the same value at one cell. Naming a claim the cell does not hold is a refuse (§10.3), not a silent no-op; `get(K.s.a)` lists what is actually there. `forget(c)` removes the claim `c` a read returned, which is how one of two claims with one value from one source is chosen. `forget(K.s.a, datetime("…"))` addresses by **time** instead of by value: it removes the values at that cell whose window covers the instant. An instant no value covers removes nothing and is not an error — unlike the claim-scoped form, it names a moment rather than a claim, and a moment nothing was true at is an ordinary answer. ### 7.6 Disagreement between sources Two claims may stand at one location with **different values from different sources**. That is a state the store holds, not an error. - **Both claims remain.** Neither write fails and neither claim is discarded, whether the second is written with `remember` or with `update`: `update` revises only its own source's claim (§7.5). Reads at the location keep working. - **Disagreement is between VALUES at a location.** It is a property of what the claims say. Which caller recorded a claim does not enter into it, and neither does which source each one cites: a rule that concludes on both sides of a disagreement is a single producer, and the location it concludes at is in disagreement like any other. `conflicts()` and `caveats()` report it. - **Nothing resolves a disagreement for you.** The service never chooses between the claims and never marks a disagreement settled. No verb takes a disagreement as its argument. What it will do is **record which source you act on**: `prefer(K.s.a, src)` states that at this location you go by `src`. That is your judgment held beside the claims — it changes no claim, discards nothing, and leaves the location in disagreement. - **The two sides are never one recorded value and one derived value.** A disagreement is between two recorded values, or between two values rules derived. A field a rule or computed field derives cannot also be written (§13.7, §14.3), so a recorded value is never added beside a derivation at one location. - **A disagreement ends when the claims change.** `forget` one of them, or have one source `update` its claim to the value the other holds, and the location is no longer in disagreement. It clears as a consequence of ordinary claim writes — that is the only way it clears. - **A standing rule reads every value at a location in disagreement** (§14) and derives from each one that satisfies its condition. Two sources disagreeing `m` against `n` therefore yield two conclusions, one drawn from each, and the disagreement travels into the conclusion rather than stopping at the premise. Deriving from both sides is not a resolution: nothing chooses between the claims, and the location stays in disagreement. - **A preference names a source that is there.** `prefer(K.s.a, src)` refuses at a location holding no value, and when `src` recorded no value at that location — a preference over nothing is a mistyped address or source, not a judgment (§7.5). A location with one value is not in disagreement, and you may still prefer its source there. - **A preference covers the sources it weighed.** It holds until you withdraw it with `unprefer(K.s.a)`. If a further source later disagrees at that location, that is a disagreement you have not spoken to, and `.preferred` reads `False` on every claim there until you state a preference over the sources now present — which may be the same source again: stating it over the new set is a new judgment, and is refused as already in effect only while the preference still covers every source there. Deriving is unaffected either way — a preference records a judgment and marks a claim; it does not steer a rule. `conflicts()` (§7.1) answers which locations are in disagreement at the moment you call it; `caveats(K.s)` asks the same question about one subject. A read of a location where you have stated a preference returns **every** claim standing there, exactly as it did before — a preference marks a claim, and changes neither what a read gives back nor what a rule derives. Each claim carries **`.preferred`** (§5.2): `True` on the one you act on, `False` on the rest. It reports whether your judgment is in force, so it reads `False` on every claim at a location where a source you have not weighed is disagreeing. `current(K.s.a)` still refuses a location holding competing values; use `get(K.s.a)`. It refuses on the CLAIMS standing there, not on how many sources wrote them — a rule reading a location in disagreement concludes once per side (§14.4), and all of its conclusions cite the same `derived` provenance, so a derived location can hold competing values from one source. `verify(K.s.a, v)` answers at such a location rather than refusing, and it answers about **every** claim standing there: `.match` is `True` when `v` is one of the competing values, so at a location holding both `"20"` and `"99"`, verifying either one matches. Both are recorded there, and the store reports what it holds rather than choosing between them. Because there is no single value at such a location, `.actual` reports **none** — it is the empty string — and **`.conflicted`** (§5.2) is `True` to say why. That distinction is the point of the field: an empty `.actual` also arises at a location where nothing has been written, and the two are not the same observation. `.conflicted` is `False` there, and `False` at any ordinary location, where `.actual` reports the value that stands. A **source-scoped** `verify(K.s.a, v, src)` is never marked conflicted. It asks what one source recorded, which has a single answer however loudly the others disagree — so `.actual` carries that source's value even at a location in disagreement. --- ## 8. Built-in operators and functions An operator given operands it does not take is a type_error. Where CPython raises a `TypeError` over the same operands, each of a class CPython has — a number, a string, a list, a tuple, a set, a dict, a view or an iterator — the refusal is CPython's sentence, naming the classes as CPython does (a `real` is its `float` and a `string` its `str`): `unsupported operand type(s) for -: 'str' and 'str'`, `'<' not supported between instances of 'str' and 'int'`, `can only concatenate list (not "int") to list`, `can't multiply sequence by non-int of type 'float'`, `bad operand type for unary -: 'str'`, `argument of type 'int' is not iterable`, and an augmented operator named with its `=`, as in `unsupported operand type(s) for +=: 'int' and 'str'`. Where an operand is of a type CPython has no class for — a `value` read from a claim, a `datetime`, a `duration` — or is an instance, or where CPython answers and this language refuses, the refusal names the operand types the operator takes. A `range` is a `list` (§5.3), so a refusal names it `list`. ### 8.1 Comparison | Operator | Signature | |----------|-----------| | `==`, `!=` | any two values `→ bool`. Values of different types are **unequal**, as in Python: `"7" == 7` is `False`, and so are `[1] == ["a"]` and `[1] == (1,)`. Numbers compare **exactly** by value across `int`, `real`, `bool` and a `value` holding a number (§5.6): `1 == 1.0` is `True`, and a `bool` is the `int` it is, `True == 1` is `True`. A `complex` (§3.5.15) 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`, `1j == 1` is `False`. A `value` compares with any value as the class it holds. Two lists, tuples (of any two lengths), sets or dicts compare element by element, at any depth, and two discriminators `D` compare by `.kind`. An iterator (§8.6) is equal only to itself, as in CPython. A keys or an items view (§5.1) equals a set, or another keys or items view, holding the same elements, and a values view is equal only to itself, as in CPython. Two operands are refused: a class instance (below), and an answer read from the store — a claim, a claim list, a proof and the other shapes of §7 — against anything but its own type | | `<`, `>`, `<=`, `>=` | `(N, N) → bool` for numeric operands `N ∈ {int, real, bool, scientific}` or a `value` holding a number (mixed classes compare **exactly** by value, and a `scientific` by its **magnitude**), `(string, string) → bool` **lexicographically** by character (Unicode codepoint order), `(datetime, datetime) → bool` **chronologically**, `(duration, duration) → bool` by **length**, `(list, list) → bool` for an orderable `T` (two element types that meet count as one `T`, as for `==`, and so do a `scientific` and a number), `(tuple, tuple) → bool` for two tuples of any lengths whose positions are orderable and meet where both have one — lists and tuples compare **element-wise lexicographically** — or `(set, set) → bool` **by inclusion**, for any two sets, and so do `(dict_keys, dict_keys)` and `(dict_items, dict_items)` and any pairing of a set with either view, each view compared as the set of its elements (§5.1) | | `in`, `not in` | `(string, string) → bool` — substring **membership** (`sub in s` is `True` when `sub` occurs anywhere in `s`; the empty string is in every string) — or element **membership** `→ bool`: `x in xs` over a constructed `list`, `x in s` over a `set`, `x in t` over a `tuple`, or `k in d` over a `dict` (testing its **keys**), `k in d.keys()` the same way, `x in v` over a values or an items view (§5.1), or `x in e` over an iterator (§8.6), which reads it up to and including the first element found — each element is compared with `x` as `==` compares them, so a number is found among numbers of either class by value (`1 in [1.0]` is `True`) and an item of another type is not found (`"a" in [1]` is `False`); `not in` is the negation of any | | `is`, `is not` | `(C, D) → bool` for two collections `C, D ∈ {list, set, dict}` — **identity**: `a is b` is `True` when the two are one collection (§8.14), so a name bound to another's list `is` it and a copy is not; `is not` is the negation. A number, a string or any other value is not an operand (below) | **String ordering** (`<`, `>`, `<=`, `>=` on two strings) compares **lexicographically** — character by character in Unicode codepoint order, the shorter string ordering first when it is a prefix of the longer (`"a" < "ab"`). So an uppercase letter orders before a lowercase one (`"Z" < "a"`), matching Python. Each ordered comparison takes two operands of the same family — two numbers, two strings, two `datetime`s, two `duration`s, or two collections; a string compared against a number is a type_error (no coercion). A `complex` has **no order**: `<`, `>`, `<=` and `>=` with one on either side is a type_error, stated as Python states it (`'<' not supported between instances of 'complex' and 'int'`), and so is everything that ranks its operands — `sorted`, `min`, `max`, a list or tuple ordered against another. Compare one with `==` and `!=`, or order by a part or the magnitude: `sorted(zs, key=abs)`. Comparisons **chain** (`a < b < c` is `(a < b) and (b < c)` with `b` evaluated once, §4.6). **Infinity and NaN.** `inf` and `-inf` order beyond every other number, as in Python. A NaN compares as IEEE 754 has it: `math.nan == math.nan` is `False`, `math.nan != math.nan` is `True`, and every ordered comparison with one is `False`. A collection is where DKE Python departs, because it has one NaN (§3.5.7) and Python finds a NaN only when it is the very object: - Membership, `.index`, `.count` and a dict's keys find the NaN by value, and a set or a dict holds it once. `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 answers `False`, `KeyError`, `2` and `False`. - Sorting puts a NaN after `inf`, as IEEE 754's total order does: `sorted([3.0, math.nan, 1.0])` is `[1.0, 3.0, nan]`, where Python's leaves the list in an order that depends on where the NaN stood, `[3.0, nan, 1.0]`. - `min` and `max` answer `nan` when any operand is one, as IEEE 754's `minimum` and `maximum` do: `max(1, x)` and `min(x, 1)` are `nan` for a NaN `x`, where Python answers `1` and `nan` by position. With a NaN present, `min(xs)` can therefore differ from `sorted(xs)[0]`. **Temporal ordering** (`<`, `>`, `<=`, `>=`) compares two `datetime` values **chronologically** — the earlier instant orders first — and two `duration` values by **length**, the shorter first. Both compare by value, not by spelling: `duration("PT90M") < duration("PT1H30M")` is `False` and so is `>`, because the two denote the same length (§3.5.6), while `duration("PT90M") <= duration("PT1H30M")` is `True`. Likewise a datetime written with fractional seconds orders against one written without them by the instants they denote, not by their text (`datetime("2026-01-02T03:04:05Z") < datetime("2026-01-02T03:04:05.5Z")`). A `datetime` and a `duration` are different things — an instant and a length — so comparing one against the other, or against a number or string, is a type_error. Temporal values are **orderable elements**, so they may also be sorted (`sorted(timestamps)`) and compared inside lists and tuples. **List and tuple ordering** (`<`, `>`, `<=`, `>=`) is **element-wise lexicographic** (Python): the first pair of unequal elements decides, and if one sequence is a prefix of the other the shorter orders first (`[1, 2] < [1, 2, 3]`, `(1, 2) < (1, 3)`). Two lists must have **orderable** element types that meet, as for list equality, or that are a `scientific` and a number; two tuples must have orderable positions, and the positions both have must meet — their lengths may differ, as two lists' may (a homogeneous or heterogeneous fixed tuple, or a variadic tuple). So `[1, 2.5] <= [1.0, 2.5]` and `(1, 2.5) < (1.0, 3)` are `True`, `(1, 2) < (1,)` is `False`, and `() < (1,)` is `True`. A shared position of another kind is a type_error: in `(1, "a") < (1, 2)` the second positions are a `string` and an `int`. An **orderable** element type is a numeric type, `scientific`, `string`, `datetime`, `duration`, or a **nested list/tuple whose own element types are (recursively) orderable** — so the comparison recurses all the way down (`[[1], [2]] < [[1], [3]]`; `[[2]] < [[10]]` is `True` because element `2 < 10`, not a comparison of rendered text). A `value` holding a number (§5.6) is a numeric element, and at every depth an `int` and a `real` compare by value, so an equal pair is passed over as Python passes it: `[1, 2.5] < [1.0, 3]` is `True`. A non-orderable element (e.g. `[1] < ["a"]`, mixing `int` and `string`) is a type_error. An empty list orders before any non-empty list (`[] < [1]`). **Set ordering** (`<`, `>`, `<=`, `>=` on two sets) is **inclusion**, as in Python: `a <= b` is `True` when every element of `a` is in `b`, `a < b` when also `b` has an element `a` does not, and `>=` and `>` are the same with the operands swapped. Elements are found as `==` compares them, so numbers are found by value exactly (`{1} <= {1.0}` is `True`, `{2**53 + 1} <= {2.0**53}` is `False`) and an element of another type is not found (`{1} <= {"a"}` is `False`). Inclusion is a **partial** order: `{1} < {2}` and `{2} < {1}` are both `False`, and so is `{1} == {2}`. That is why a set is not an **orderable** element (the list above): `sorted`, `min`, `max` and the ordering of two lists or tuples of sets are type_errors, where Python answers them in an order that depends on the input's. **Numeric equality** (`==` / `!=`) compares an `int` and a `real` **exactly**, by the values they hold: `1 == 1.0` is `True`, `2.0 == 2` is `True`, `1 == 2.0` is `False`, and `2**53 + 1 == 2.0**53` is `False` although that `int` becomes `2.0**53` when it is converted to a `real`. The ordered operators, membership, set identity, `sorted`, `min` and `max` compare the two the same exact way, at every magnitude, matching Python. A `bool` is the `int` it is, as in Python: `True == 1` is `True`, `False == 0.0` is `True`, `True < 2` is `True`, and it compares, orders, dedups and is found the same exact way. **A `scientific` orders by its magnitude.** Against an `int`, a `real`, a `bool` or another `scientific`, the ordered operators read a `scientific` as the `real` nearest the number it writes, and compare that exactly, as two numbers compare above. So `scientific("2.0e4") > 100` and `1.5 < scientific("2.0e0")` are `True`, and a list of `scientific`s sorts from the smallest magnitude up. Its significant figures take no part in the order: `scientific("6.022e23")` and `scientific("6.0220e23")` are neither `<` nor `>` each other, and `<=` and `>=` both hold. Equality keeps them, and keeps the class: those two are unequal (§3.5.14), and `scientific("1.0e0") == 1` is `False`, though `<=` and `>=` both hold there too. A rule premise orders a stored `scientific` the same way (§14.2), so a program and a rule give one answer to one comparison. `sorted`, `min` and `max` take a collection of `scientific`s, and a list or a tuple of them orders against one of numbers; a collection that holds a `scientific` together with another class of number orders the same way, each element keeping its class (§5.6): `sorted([scientific("2.0e0"), 1, 2.5])` is `[1, scientific("2.0e0"), 2.5]`, `min(scientific("1.0e0"), 2)` is `scientific("1.0e0")`, and `[scientific("1.0e0"), 0] < [1, 5]` is `True`. Its elements still compute nothing, so `sum` over it is a type_error. **Values of different types** are unequal. `==` takes any two values and `!=` is its negation, so `"7" == 7` is `False` and `"7" != 7` is `True`, as in Python; no value is converted to another's type to be compared. The rule holds at every depth and in every comparison built on `==`: membership, `.index`, `.count`, `.remove`, a dict key read, set inclusion and difference, a literal `case` over a `value`, and `is`. A search that finds nothing answers as it would for any absent value, with the catchable refuse Python raises (`[1].index("a")` refuses as `'a' is not in list`, `{1: 2}["a"]` as `'a'`). Ordering is a different question: `<`, `>`, `<=` and `>=` still require two values of one family, since Python raises there too (`"a" < 1` and `[1] < ["a"]` are type_errors). **A `bool` is an `int`, by value and by class.** Wherever a value is compared or computed with — an operator, a fold, membership, a set or a dict key, a literal `case`, a rule premise and a query verb (§14.2, §7) — a `bool` is the number it is. And wherever a program names an `int`, a `bool` fills it, as in Python, where `bool` is a subclass of `int`: a parameter, annotation or return declared `int`, an index, a bound and a repetition count (§6.4), and a class arm `case int` over a `value`, which takes a `True` (§6.2). It keeps its class throughout, so it prints as `True`. What does not widen is a collection's element type: a `list` is not a `list` (§6.4). **Class instances** are not compared by `==` / `!=`. The table above lists every operand pair the two operators accept, and a class instance is not among them, so comparing two instances is rejected whether or not they share a class. Compare a **field** of each instead, or use `isinstance(x, C)` to test an instance's class. Membership is deliberately different: `x in xs` over a `list` IS defined, and compares by an instance's identity — its subject together with its concrete class — so a `C` built on one subject is not found in a list holding a `C` built on another. An instance therefore HAS an identity in this language; what it does not have is an equality operator. **Identity** (`is` / `is not`) asks whether two lists, sets or dicts are one collection. Each is shared (§8.14): every name, parameter, element and loop variable holding it holds the same one, and `is` answers as Python does. After `b = a`, `a is b` is `True`; `a.copy()`, `list(a)`, a comprehension and a literal each build a new collection, so `a is a.copy()` and `[1] is [1]` are `False`, and an element read is the collection its container holds, so `m[0] is m[1]` is `True` after `m = [row, row]`. `is not` is the negation, and both chain as every comparison does. 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 no identity 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 there is no answer a DKE Python program could match, and `is` there is a compile-time error naming `==` or `!=`. Over any other value — a tuple, a class instance, a `datetime` — the error names the three kinds `is` takes. A rule premise compares a field's value, so `is` there is refused as it is over a number. `x is None` and `x is not None` have no `==` form, because DKE Python has no `None`: a rule asks that a field has no value with `absent(…)` (§14.2). Writing either is a compile-time error naming no comparison — and the absence premise, in a rule premise that can take one. `is` is reserved (Appendix B), as it is in Python, and names nothing: `is = 1` is a parse_error at column 1. **List equality** (`==` / `!=`) is element-wise: the two lists must have element types that **meet** — one type, numbers of either class, a `complex` and a number (§3.5.15), or collections whose own elements meet, at any depth — and each pair of elements compares as `==` compares them, so `[1] == [1.0]` and `[[1], [2.5]] == [[1.0], [2.5]]` are `True`, as in Python. Elements of different types are unequal, so `[1] == ["a"]` is `False`. An empty `list` compares with any list (`[] == [1]` is `False`), and lists compare unequal when their lengths differ or any pair of elements differs (recursively, for nested lists). **List membership** (`x in xs`) tests a constructed `list`, comparing `x` with each element as `==` does. A number is found among numbers of either class by value, as `1 == 1.0` compares: `1 in [1.0]` and `2.5 in [1, 2.5]` are `True`, and so are `[1.0].index(1)` and `[1, 1.0].count(1)` counting `2`. An item of another type is not found: `"a" in [1, 2]` is `False` and `[1, 2].count("a")` is `0`. An element that is a collection is found by list equality, so `[1] in [[1.0]]` is `True`. The `in`/`not in` operator does **not** test a store-derived set; to walk one, use the `for in :` statement (§6.3, §9.2.2), whose `in` is the loop keyword, not this operator. **Tuple equality** is position by position, so `(1, "a") == (1.0, "a")` is `True`. Their lengths may differ, and two tuples of different lengths are unequal: `(1, 2) == (1,)` is `False`, and so is `(1,) == ("a",)`, whose one position holds another type. **Set equality** is element-wise and order-independent — two sets are equal when they hold the same elements, and numbers are equal by value as a set's members are: `{1, 2.0} == {1.0, 2}` and `{(1,)} == {(1.0,)}` are `True`, while `{1} == {"a"}` is `False`, and so is a set compared with a list or a tuple. Two dicts are equal when they map equal keys to equal values: `{"a": 1} == {"a": 1.0}` and `{1: "a"} == {1.0: "a"}` are `True`. **Set membership** (`x in s`), **dict membership** (`k in d`, over keys), and **tuple membership** (`x in t`, over every position) compare the item as `==` does. A set and a tuple find a number among numbers by value, as a list does (`1.0 in {1, 2.5}` is `True`), and so does a dict among its keys, as `d[k]` and `d.get` do (§3.5.12). An item of another type is not found, and a record tuple is searched for any of its fields: `"alice" in ("alice", 30)` is `True` and `"a" in {1: 2}` is `False`. **Dict equality** (`==` / `!=`) is **order-independent** (Python dict equality): two dicts of a matching key and value type are equal when they hold the same key→value mapping, regardless of insertion order (`{"a": 1, "b": 2} == {"b": 2, "a": 1}` is `True`). Keys and values compare as list elements do, so numbers of either class compare by value, and a key or value of another type is unequal: `{"a": 1} == {"a": "x"}` is `False`. An empty dict compares with any dict, so `{} == {"a": 1}` is `False`. A `value` (§5.6) may be compared with `==` / `!=` against any value without first destructuring it — a literal of any of its classes, another `value`, or a collection; the result is `bool` regardless of the runtime class, and a class mismatch compares **unequal** rather than coercing (`c.value == 41` is `False` when the content is text, even the text `"41"`, and `c.value == "True"` is `False` when it holds `True`). This mirrors Python's cross-type `==`. Ordering (`<`, `>`, …) requires operands of the same family, so a `value` must be destructured through `match` (§5.6) into an orderable arm before it can be ordered — unless it holds a number (§5.6), when it orders and compares against any number as the number it holds (`[1, 2.5][0] < 2` and `[1, 2.5][0] == 1.0` are `True`). ### 8.2 Logical | Operator | Signature | |----------|-----------| | `and` | `(T, U) → T ⊔ U` — short-circuit, **operand-return** | | `or` | `(T, U) → T ⊔ U` — short-circuit, **operand-return** | | `not` | `any → bool` | **Truthiness.** The eight read-result types have no truth value. Every **other** value has a truth value and may be used where a condition is expected — an `if` / `elif` condition (§9.2.1), a comprehension `if` filter (§8.9), the condition of a conditional expression `a if c else b` (§8.10), and the operands of `and` / `or` / `not`. **Falsy** values are `False`, `0`, `0.0` (and `-0.0`), a `complex` whose parts are both zero (`0j`), a zero `scientific`, a zero `duration`, the empty string `""`, an empty `list` / `set` / `dict` / `tuple`, an empty `blob`, and `None`; every other value — every non-zero number, every non-empty container, every `datetime`, every class instance, every iterator (§8.6), used up or not — is **truthy**. This matches CPython for every type that has a truth value, and the exclusion below is a deliberate divergence rather than a gap. **The eight read-result types have no truth value**: `active_claim`, `claim_list`, `proof_tree`, `diagnosis`, `subject_set`, `string_list`, `verify_result`, `result_type_set`. Each of the four positions above refuses one, and the refusal names that position. This **diverges from CPython on purpose**, where an empty container is falsy. A store-derived collection that came back empty and a read that found nothing to tell you are different answers, and a truth value collapses them into one `False` — so `if claims:` would read as "there are claims" while also meaning "the read had nothing to say". Test what you mean instead: `.length` or `len()` for emptiness (§8.6), and `for … in` to walk (§5.1). **`and` / `or` return an operand, not a coerced bool.** `a and b` evaluates `a`; when `a` is **falsy** it returns `a` (leaving `b` unevaluated), otherwise it returns `b`. `a or b` evaluates `a`; when `a` is **truthy** it returns `a` (leaving `b` unevaluated), otherwise it returns `b`. So `name or "anonymous"` is `name` when it is a non-empty string and `"anonymous"` when it is empty; `count or 0` is the first non-zero of the two; `x > 0 and label` is `label` when `x > 0`, else `False`. Both are **short-circuit** — the right operand is evaluated only when the left does not already decide the result, so a right operand that would refuse is not reached when the left short-circuits. The **result type** is the join `T ⊔ U` of the two operand types: identical types collapse to that type; an `int` / `real` pair joins to `real` without converting the operand returned (`1 or 2.5` is `1`, §3.5.7); an empty-list operand unifies to the concrete list type; and two **different value scalars** (`x > 0 and "yes"` → `bool` or `string`) yield a `value` (§5.6 — expressible and equatable). Two operands with no common result type — e.g. a `list` and a `dict` — are a type_error (`and` / `or` must be able to name the type of whichever operand it returns). For two `bool` operands the result is a `bool`, identical to a plain boolean `and` / `or`. **`not` always yields a `bool`.** `not x` is `True` when `x` is falsy and `False` when `x` is truthy, for any value `x`. DKE Python uses the **word forms** `and` / `or` / `not`; there are no `&&` / `||` / `!` tokens. In precedence (§4.6) `or` binds loosest, then `and`, then `not`, then the comparison operators — so `not a == b` is `not (a == b)` and `a or b and c` is `a or (b and c)`. ``` # operand-return.dpy — `or` and `and` return one of their operands, not a bool. def operands(): name = "" print(name or "anonymous") # anonymous count = 3 print(count > 0 and "some") # some zero = 0 print(zero and 10 // zero) # 0 ``` The last line never divides: `zero` is falsy, so `and` returns it and `10 // zero` is not evaluated. ### 8.3 Arithmetic | Operator | Signature | |----------|-----------| | `+` | `(int, int) → int`; `(N, N) → real` when at least one operand is `real`; `(complex, complex) → complex`, and a `complex` with an `N` on either side | | `-`, `*` | `(int, int) → int`; `(N, N) → real` when at least one operand is `real`; `(complex, complex) → complex`, and a `complex` with an `N` on either side | | `/` | `(N, N) → real` — **true division** (always a `real`, including `int / int`: `7 / 2` is `3.5`); `(complex, complex) → complex`, and a `complex` with an `N` on either side | | `//` | `(int, int) → int`; `(N, N) → real` when a `real` operand — **floor division** (rounds toward −∞: `-7 // 2` is `-4`) | | `%` | `(int, int) → int`; `(N, N) → real` when a `real` operand — **floor modulo** (result takes the sign of the divisor: `-7 % 2` is `1`, `-7.5 % 2` is `0.5`) | | `%` (string) | `(string, any) → string` — **percent formatting**, as CPython's: each conversion takes the next element of a `tuple` on the right, or the one value that is not. `%(key)d` looks its key up in a `dict` or a `list`. The conversions are `s` `r` `a` `d` `i` `u` `o` `x` `X` `e` `E` `f` `F` `g` `G` `c`, with the flags `-` `+` ` ` `#` `0`, a width, a precision and `*` for either; `%%` is a `%`. Every fault is a catchable refuse as the program runs, in CPython's words: `"%s %s" % 1` refuses with `not enough arguments for format string` | | `@` | CPython's **matrix product**, of `%`'s level, and `@=` beside it. No value here answers it, so every one is a type_error naming its operands: `1 @ 2` refuses with `unsupported operand type(s) for @: 'int' and 'int'` | | `**` | `(int, int) → int` when the exponent ≥ 0; `(N, N) → real` when a `real` operand, or when a negative exponent is **written as a literal** (§8.3, *Value-dependent typing*) — **power** (right-associative) | | `+` (sequence) | `(list, list) → list<…>` — the element type unifies as a list literal's does, so `[1] + ["a"]` is `list` and `[] + xs` takes `xs`'s type; `(tuple<…>, tuple<…>) → tuple<…>` — the POSITION lists concatenate, so `tuple + tuple` is `tuple`. Two strings concatenate too (§8.4) | | `*` (repetition) | `(string, int) → string`; `(list, int) → list`; `(tuple, int) → tuple` — **either order** (`"ab" * 3` and `3 * "ab"`). The count is an `int`; a `real` count is a `type_error`, and a count ≤ 0 gives the empty sequence. A `tuple`'s type IS its position list, so its count must be **written as a literal** (§8.3, *Value-dependent typing*) and the result may name at most 65536 positions | | `-` (prefix) | unary negation: `int → int`, equivalent to `0 - x`; `bool → int` (`-True` is `-1`); `real → real`, keeping the sign of zero; `duration → duration`; `complex → complex`, negating both parts | | `~` (prefix) | bitwise inversion: `int → int`, equivalent to `-x - 1` (`~5` is `-6`); `bool → int` (`~True` is `-2`) | | `+` (temporal) | `(datetime, duration) → datetime` (either order); `(duration, duration) → duration` | | `-` (temporal) | `(datetime, duration) → datetime`; `(datetime, datetime) → duration`; `(duration, duration) → duration` | | `*` (temporal) | `(duration, N) → duration` (either order) — scale a length | | `/` (temporal) | `(duration, N) → duration` — split a length; `(duration, duration) → real` — how many times one fits into the other | | `%` (temporal) | `(duration, duration) → duration` — the length left over | | `-` (set) | `(set, set) → set` — **difference**, `a.difference(b)` (§8.6). A keys or an items view (§5.1) on either side is read as the set of its elements, and beside one any collection is read as `set(...)` reads it (§8.13): `["a", "b"] - {"a": 1}.keys()` is `{'b'}` | | `\|`, `&`, `^` | `(int, int) → int`, a `bool` counting as the `int` it is, and `(bool, bool) → bool` — **bitwise or**, **and** and **exclusive or** (*Bitwise operators*, below): `12 & 10` is `8`, `True ^ True` is `False`. `(set, set) → set<…>` — **union**, **intersection** and **symmetric difference**, `a.union(b)`, `a.intersection(b)` and `a.symmetric_difference(b)` (§8.6). They take two integers, two of a set, a keys view and an items view (§5.1) in any pairing, or a keys or an items view beside any collection on either side, a view read as the set of its elements and a collection as `set(...)` reads it (§8.13). `\|` also takes two dicts, `(dict, dict) → dict<…>` — a **merge**, the same as `{**d, **e}` (§3.5.12), and `d \|= e` changes `d` in place, `e` being anything `update` reads (§8.14), as in `d \|= [("b", 2)]`. They take nothing else | | `<<`, `>>` | `(int, int) → int`, a `bool` counting as the `int` it is — **left** and **right shift** by a count of bits: `1 << 4` is `16`, `-5 >> 1` is `-3` (*Bitwise operators*, below) | The numeric operand set is `N ∈ {int, real, bool}`. A `bool` computes as the `int` it is, as in Python — `True + True` is `2`, `True / 2` is `0.5`, `sum([True, True])` is `2` — so wherever the rules below say `int`, a `bool` counts as one. A `value` holding a number (§5.6) is a numeric operand too: its result is a `real` wherever the rules below make it one for either class, and otherwise a `value` holding a number. `+`, `-`, `*`, `//`, `%`, `**` **promote**: when both operands are `int` the result is `int`; when at least one is `real` the result is `real`. `/` is the exception — it is **true division** and always produces a `real`, so `int / int` promotes (`7 / 2` is `3.5`, `8 / 2` is `4.0`); a floored integer quotient is `//` (`7 // 2` is `3`). `//` and `%` are the matched **floor** pair: both round toward −∞ / take the sign of the divisor, so the identity `a == (a // b) * b + (a % b)` holds — and it holds at both operand classes, so `-7.5 % 2` is `0.5` just as `-7 % 2` is `1`. A remainder of zero takes the divisor's sign as well, which is visible only on a real: `6.0 % -2` is `-0.0`. **Complex arithmetic.** `+`, `-`, `*`, `/` and `**` take a `complex` (§3.5.15) beside another `complex` or beside a number, which computes as the complex with that real part, and answer a `complex`: `(3 + 4j) * 2` is `(6+8j)`, `(3 + 4j) / 2` is `(1.5+2j)`, `1 / (3 + 4j)` is `(0.12-0.16j)`, `True + 1j` is `(1+1j)`. Each answers what CPython answers for the same operands, the signs of zero parts included. An `int` operand computes as the `real` nearest it, as in Python, though `==` compares it exactly (§8.1): `(2 ** 53 + 1) + 0j` is `(9007199254740992+0j)`, and `2 ** 53 + 1 == 9007199254740992 + 0j` is `False`. The prefix `-` negates both parts. `//` and `%` do not take a `complex`, and neither do `divmod`, `round`, `int` and `float`: each is undefined for one in Python as well. A division by a zero of any class refuses at runtime with the `division_by_zero` discriminator, in Python's words, `complex division by zero`. A part may be infinite or a NaN, in an operand or in the result, and computes as *Infinity and NaN* (below) says. `**` raises a `complex` to a number or a `complex`, or a number to a `complex`, and answers a `complex` computed as Python computes it, each step correctly rounded (below): `1j ** 2` is `(-1+0j)`, `(3 + 4j) ** 0.5` is `(2+1j)`, and `2 ** 1j` is `(0.7692389013639721+0.6389612763136348j)`. An exponent that is a whole number no larger than 100 in magnitude is computed by repeated multiplication, so `(1 + 1j) ** 100` is `(-1125899906842624+0j)` exactly. A zero base under a negative or a complex exponent refuses with the `division_by_zero` discriminator, in Python's words, `0.0 to a negative or complex power`; a base that is not zero never does. A part past the range is `inf` or `-inf`, where Python raises `OverflowError: complex exponentiation`, and *Infinity and NaN* (below) gives the rest. **Bitwise operators.** `&`, `|`, `^` and the prefix `~` compute on an `int`'s two's-complement bits, as Python's do, so `-12 & 10` is `0`, `-12 | 10` is `-2` and `~5` is `-6`; each answers an `int` in range. Two `bool`s given `&`, `|` or `^` answer a `bool`, as in Python, and `~` and the shifts answer an `int` for a `bool` too: `~True` is `-2`, `True << 2` is `4`. `>>` rounds toward −∞, as `//` by a power of two does, so `-5 >> 1` is `-3`, and a count of 64 or more answers `0` or `-1` by the sign. `<<` multiplies by a power of two: `-1 << 63` is `-9223372036854775808`, and a result outside the `int` range refuses with `arithmetic_overflow`, where Python answers a larger integer. A negative count refuses either shift with the `value_error` discriminator, in Python's words, `negative shift count`. An operand that is not an `int` or a `bool` is a `type_error`, a `set` included for the shifts and `~`. **Temporal arithmetic.** `datetime` and `duration` compute together, with the operand types deciding what an operation means. Adding a `duration` to a `datetime` **shifts** the instant (`t + duration("PT1H")` is an hour later, and subtracting shifts it earlier); subtracting one `datetime` from another gives the **length between them**; and two `duration`s add and subtract to a `duration`. A `duration` also **scales**: multiplying by a number stretches it (`duration("PT30M") * 3` is `PT1H30M`, `duration("PT1H") * 2.5` is `PT2H30M`) and dividing by a number splits it (`duration("PT1H") / 4` is `PT15M`). Dividing a `duration` **by another `duration`** asks how many times one fits into the other and yields a plain `real` — the answer has left the temporal domain, so `duration("PT3H") / duration("PT45M")` is `4.0`, not a length. The matching `%` gives the length **left over**: `duration("PT3H10M") % duration("PT45M")` is `PT10M`. Shifts respect the calendar, so `datetime("2024-02-28T12:00:00Z") + duration("P1D")` is `2024-02-29T12:00:00Z` in a leap year and `2023-03-01T12:00:00Z` in a common one. Addition, subtraction, `%`, and scaling by an `int` are **exact to the nanosecond** — no result is rounded. Scaling by a `real` (`*` or `/`) rounds to the **nearest nanosecond**, halves away from zero, since a fractional nanosecond is not a `duration`. Combinations that name nothing are type errors: two `datetime`s have no **sum** (only a difference), a `duration` minus a `datetime` is meaningless (the operand order carries the meaning), a `duration` times a `duration` is an area rather than a time, a number divided by a `duration` is a frequency rather than a time (division is not commutative here), and `//` and `**` are not defined over temporal values. Subtracting a later instant from an earlier one yields a **negative** `duration` (§3.5.6), which renders and reads back with a leading `-`. A result whose span exceeds what a `duration` holds (about ±292 years), or whose instant falls outside years 0001 through 9999, is a **catchable refuse** rather than a wrapped value; so is dividing or taking a remainder by a zero-length `duration`. **Sequence repetition.** `*` is additionally overloaded so a `string`, a constructed `list` or a constructed `tuple` may be repeated by an `int` count (either order): `"ab" * 3` is `"ababab"`, `"-" * 5` is `"-----"`, `[0] * 4` is `[0, 0, 0, 0]`, `[1, 2] * 3` is `[1, 2, 1, 2, 1, 2]`, and `(1, 2) * 2` is `(1, 2, 1, 2)`. A `string * int` yields a `string`; a `list * int` yields a `list`. A **non-positive** count yields the empty sequence (`"x" * 0`, `[9] * -1` and `(1, 2) * 0` are `""`, `[]` and `()`), matching Python. The count MUST be an `int` — a `real` or non-`int` repeat count, and a `string`, `list` or `tuple` multiplied by anything but an `int`, is a type_error. The result is bounded (the count is a finite `int`), so termination (§9.5) is preserved. A `tuple` repeats under one further condition, and the reason is its type. A `string` and a `list` keep their own type however often they are repeated — length is in neither — while a tuple's type IS its position list (§3.5.11, §5.3), so `(1, 2) * 2` is a `tuple` and a constant index into it still reads the position it names. Naming that type needs the count, so the count must be **written as a literal**: the value-dependent typing rule below. `(1, 2) * n` for a count the run supplies is a type_error. Its message says so, and names what to write when the count really is decided as the program runs: repeat a `list`, whose length is outside its type, and convert — `tuple([1, 2] * n)` gives a `tuple`. A tuple built that way is a **variadic** tuple (§5.1), and it repeats by any count at all, literal or not, because it has no arity in its type to name. A repeated tuple type may name at most **65536** positions; a literal count whose product exceeds that is a type_error rather than a very long type, and the `list` remedy has no such ceiling. **Value-dependent typing.** A few results are typed by an operand's **value** and not by its type alone. One rule governs every one of them, and it is stated here once rather than at each: *a static type may depend on an operand's value only where the program **writes** that value as a literal.* An integer expression over literals folds to a literal before the type is decided (§8.3, above), so `(1, 2) * (1 + 1)` counts as written; a name, a parameter, a field read or a call result does not. The same test decides the type and what the expression does when it runs, so the two never disagree about which case a program is in. Three rules use it, and they are applications of one rule rather than three exceptions: - `**` gives a `real` for a negative `int` exponent when the exponent is a literal — `2 ** -1` is `0.5` (§8.3, above); - `*` repeats a `tuple` when the count is a literal, giving the repeated position list — `(1, 2) * 2` is a `tuple` (§8.3, above); - `t[i]` on a tuple reads the indexed position's own type when `i` is a compile-time constant (§5.3). Where the value is not written the expression is refused — or, for `**`, is typed as an `int` and refuses when it runs — and the message says why: a type is fixed before the program runs, and it cannot be two things at once. The rule costs no function in any of the three, because every negative exponent, every repeat count and every tuple position a program can name, it names with something it wrote. **`abs(x)`.** The built-in `abs` returns the absolute value of a number: `abs(x)` takes exactly one operand and preserves its numeric type — `abs(-5)` is the `int` `5`, `abs(-3.5)` is the `real` `3.5`. The `abs` of a `complex` is its **magnitude**, a `real`: `abs(3 + 4j)` is `5.0`. A magnitude past the largest `real` is `inf`, where Python raises `OverflowError: absolute value too large` (`abs(complex(1.7e308, 1.7e308))`), and a part that is infinite makes it `inf` and one that is a NaN, beside no infinite part, makes it `nan`, as in Python. An argument that is neither a number nor a `complex`, or an argument count other than one, is a type_error. `abs` of the most-negative `int` refuses at runtime (its negation is out of range), the same as unary minus (§8.3 above). **`ord(s)`, `chr(i)`.** The Unicode codepoint round-trip. `ord(s)` takes a **one-character** `string` and returns its integer codepoint (`ord("A")` is `65`, `ord("æ")` is `230`); `chr(i)` takes an `int` codepoint in `range(0x110000)` and returns the one-character `string` for it (`chr(97)` is `"a"`, `chr(937)` is `"Ω"`). A non-`string` `ord` argument or a non-`int` `chr` argument (or an argument count other than one) is a type_error. At runtime, `ord` of a string whose length is not one, and `chr` of a codepoint outside `range(0x110000)` — or of a lone surrogate (`U+D800`..`U+DFFF`), which has no UTF-8 encoding — are catchable refuses (§10.3). **`round(x[, n])`.** The built-in `round` rounds a number **half to even** ("banker's rounding"): a value exactly halfway between two candidates goes to the even one, so `round(0.5)` and `round(2.5)` are both `0` and `2`, and `round(0.125, 2)` is `0.12`. The optional second operand `n` is an `int` digit count (it may be negative). The result type follows the operands: `round(int, …)` is an `int`; `round(real)` **without** a digit count is an `int` (rounded to the nearest whole number); `round(real, n)` is a `real` — a `real` even at `n = 0`, so `round(2.5, 0)` is `2.0`. A negative `n` rounds to a power of ten (`round(35, -1)` is `40`). Because a `real` holds an IEEE-754 double, rounding follows the stored value, not the written decimal — `round(2.675, 2)` is `2.67` (the nearest double to `2.675` is slightly below it), matching CPython. A non-numeric first argument, a non-`int` digit count, or an argument count other than one or two is a type_error; a result whose magnitude leaves the `int` range refuses at runtime. Without a digit count, an infinity or a NaN refuses at runtime too, an infinity with `arithmetic_overflow` and a NaN with `value_error`, as `int(x)` does (§8.13); with one, `round(x, n)` of an infinity or a NaN is `x`, and a result past the largest `real` is `inf` or `-inf` (§8.3, *Infinity and NaN*). **`divmod(a, b)`.** Returns the pair `(a // b, a % b)` — the floored quotient and the remainder, computed in one call. Both operands must be numeric, and the pair promotes together: `tuple` when both are `int`, `tuple` when either is `real` — never a mixed pair. An operand that is a `value` holding a number (§5.6) with no `real` beside it gives a `tuple`, whose two positions still share one class when it runs. `divmod` is defined in terms of `//` and `%`, so it takes exactly the operands those two share (§8.3 above); a non-numeric operand, or an argument count other than two, is a type_error. The quotient rounds toward negative infinity and the remainder takes the sign of the **divisor**, exactly as the two operators do, so `divmod(7, 2)` is `(3, 1)`, `divmod(-7, 2)` is `(-4, 1)`, `divmod(7.5, 2)` is `(3.0, 1.5)`, and `q * b + r == a` always holds. A zero divisor is a runtime refuse (§10.3), and `divmod` of the most-negative `int` by `-1` refuses on overflow, the same as `//` and `%`. **`pow(base, exp)` and `pow(base, exp, mod)`.** With two arguments `pow` **is** `**`: `pow(a, b)` has the type, the value and any refusal that `a ** b` has, so `pow(2, 10)` is `1024`, `pow(2, -1)` is `0.5`, and a refusal names `**`, as Python's own message for either spelling does. With three it is modular exponentiation, which no operator spells. Every argument must be an `int` or a `bool`, and the answer is the `int` Python gives for `base ** exp % mod`, computed below the modulus so that an exponent of any size finishes: `pow(2, 10, 1000)` is `24` and `pow(2, 9223372036854775807, 1000000007)` is `529367677`. The answer takes the modulus's sign, as `%` does, so `pow(2, 3, -5)` is `-2`, and a modulus of `1` or `-1` gives `0`. A negative exponent raises the base's inverse modulo `mod`, so `pow(3, -1, 7)` is `5`. A base with no inverse, and a modulus of `0`, are runtime refuses (§10.3) with the `value_error` discriminator, in Python's words. A count other than two or three is a type_error. So is a non-integer in the three-argument form, in Python's words for the first class whose power Python asks, the base's, then the exponent's, then the modulus's: a `real` is refused, `pow() 3rd argument not allowed unless all arguments are integers`; a `complex`, once the base and the exponent are numbers, `complex modulo`; and where neither speaks the three classes are named, so `pow(2, "a", 5)` is refused, `unsupported operand type(s) for ** or pow(): 'int', 'str', 'int'`. A `value` is told to narrow, as it is beside any operator (§5.6). **`sum(iterable)`, `min(…)`, `max(…)`.** These reduce an in-memory iterable (a `list` / `set` / `dict` / `string` / store-derived collection), distinct from the same-named store-aggregate folds `sum(K.attr)` / `min(K.attr)` / `max(K.attr)` (§7.1.1), which take a bare `K.attr` path — the argument shape selects the form. `sum(iterable)` adds numeric elements: all-`int` → `int` (an empty iterable is `0`), any `real` element → `real`; `complex` elements → `complex` (`sum([1j, 2j])` is `3j`), and a `complex` start beside numeric elements, or a number start beside `complex` ones, gives a `complex` too; any other element is a type_error. Over elements that are `value`s holding numbers (`[1, 2.5]`, §5.6) the sum is such a `value`, of the class the additions give when it runs (`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 to the nearest `real`, ties to even, so the order the elements come in cannot change it: `sum([0.1] * 10)` is `1.0` and `sum([1e16, 1.0, -1e16])` is `1.0`, where CPython 3.11's `sum`, which rounds after each addition, answers `0.9999999999999999` and `0.0`. An `int` enters as the integer it is, so `sum([9007199254740993, 0.5])` is `9007199254740994.0`. A sum whose rounded value is past the largest `real` is `inf` or `-inf`, as `sum([1e308, 1e308])` is in Python, but `sum([1e308, 1e308, -1e308])` is `1e+308`, where Python's, which overflows on the way, answers `inf`. An infinite element adds as IEEE 754 adds, so `sum([math.inf, -math.inf])` is `nan`, and a NaN element makes the sum `nan`, as in Python. A zero sum is `-0.0` only when the start and every element are `-0.0`, and the default start is the `int` `0`: `sum([-0.0])` is `0.0` and `sum([-0.0], -0.0)` is `-0.0`, as in Python. An all-`int` sum is the exact integer, refused as `arithmetic_overflow` only when that total is outside the signed 64-bit range. A sum holding a `complex` adds one element at a time, left to right, as CPython's does. The store fold `sum(K.attr)` is exactly rounded too, `1.0` over ten stored `0.1`s (§7.1.1). A second argument, written `sum(iterable, start)` or `sum(iterable, start=…)`, is the value the elements are added to, first, as in Python, and over numbers it is an operand of the exact sum above: `sum([1, 2], 10)` is `13`, an empty iterable answers the start (`sum([], 2.5)` is `2.5`), and a `real` start makes the sum `real`. A start that is not a number takes what `+` adds to it. A `list` or a `tuple` start concatenates the elements: `sum([[1], [2]], [])` is `[1, 2]`, and `sum([(1,), (2,)], ())` is `(1, 2)`. A `duration` start adds lengths: `sum([duration("PT1H"), duration("PT2H")], duration("PT0S"))` is `PT3H`. A `datetime` start is shifted by each `duration` in turn. The sum has the type `+` gives the start and one element, and then that and a second, since the run decides how many elements there are, so the tuple sum above is a `tuple`, of runtime length. Over an empty iterable a `list` start answers a new list equal to it, not the start itself. A `string` start is a type_error in Python's words, `sum() can't sum strings [use ''.join(seq) instead]`. A start that `+` cannot keep adding the elements to is a type_error too: one that takes no element, like a `list` start over `int`s, and one that takes a single element but not a second, like a `duration` start over `datetime`s. A `tuple` element whose positions share no type is refused for the same reason. `min` and `max` take **either** one iterable **or** two-or-more scalar values, returning the smallest / largest in the order `sorted` puts them in (§8.1): numbers by value, a `scientific` by its magnitude, strings lexicographically, a `datetime` chronologically and a `duration` by length. The elements or values must share an orderable type — numeric, `scientific`, `string`, `datetime`, `duration`, or a nested list/tuple of those, in which case the comparison recurses lexicographically (§8.1). Of two equal elements the first is returned. An `int` and a `real` compare by value and the one returned keeps its class (`min([1, 2.5])` is `1`, `max([1, 2.5])` is `2.5`). `min`/`max` of a **runtime-empty** iterable is a catchable refuse (Python's `ValueError`); `min([])` on a literal empty list is instead a compile-time type_error (its element type is unknowable) — unless a keyword-only **`default=`** is given, which is returned for an empty iterable in place of the refuse (`min([], default=0)` is `0`). The `default=` is valid only with a single iterable argument (not with multiple positional values), and its type must match the element type. A keyword-only **`key=`** compares the elements by what the named function returns for each, in place of the elements themselves: `max(words, key=len)` is the longest word. The name is that of a builtin taking one argument (`len`, `abs`, `ord`, `str`, …) or of a script — declared in the same unit, imported by `from`, or qualified by its module (`key=m.f`) — and it resolves as a call to that name does. A method of a built-in type may be named as well, as in `key=str.lower`, which calls `lower` on each element, and an element of another type is a type_error, `descriptor 'lower' for 'str' objects doesn't apply to a 'int' object`. The key is called once per element, in order, after the call's other arguments are evaluated. Only the keys are compared, so the keys must be orderable and the elements need not be (`max([{"a": 1}, {}], key=len)` is `{'a': 1}`); of two elements with equal keys the first is returned. `min` and `max` take a key over one iterable or over several values (`min("bb", "a", key=len)` is `'a'`), and `default=` answers an empty iterable beside a key as it does without one, in either order. `key=` takes a name and nothing else: a `lambda` (§8.8), a method of a value (`d.get`), a call, a class or an expression is a parse_error, and so is `key=None`, CPython's spelling of no key, which DKE Python writes by leaving `key=` out. `sum` takes no `key=`, nor does the store fold `min(K.attr)`; a key whose values do not order, or which cannot take the elements, is a type_error, and so is a key over what is no collection, `sorted(1, key=abs)`, in Python's words, `'int' object is not iterable`. `**` is **right-associative** and binds more tightly than the multiplicative and unary operators (§4.6): `2 ** 3 ** 2` is `2 ** (3 ** 2)` = `512`, and `-2 ** 2` is `-(2 ** 2)` = `-4`. An `int` base raised to a **non-negative** `int` exponent yields an `int` (`2 ** 10` is `1024`); a `real` operand yields a `real`. An `int` base with a **negative** `int` exponent yields a `real` when the exponent is a **literal** — `2 ** -1` is `0.5` and `(-2) ** -1` is `-0.5`, as in Python. When the exponent is not a literal the expression is a runtime refuse instead, and the message says why: a value's type is fixed before the program runs, so `2 ** n` would have to be an `int` and a `real` at once. Write the base as a `real` (`2.0 ** n`) when the exponent can be negative. This is one application of a general rule (§8.3, *Value-dependent typing*), not a rule of its own. Division by zero (`/` or `//`, integer or real) and modulo by zero are runtime refuses (§10.3). Integer overflow — any integer `+`, `-`, `*`, `//`, `%`, `**`, `<<`, or prefix `-` whose mathematically correct result falls outside the signed 64-bit range — is a runtime refuse with a teaching message rather than wrapping. A real result whose magnitude leaves the finite range is `inf` or `-inf`, and so is such a part of a complex result (*Infinity and NaN*, below). Reals compute to the precision of an IEEE-754 double and render in the canonical decimal form of §3.5.7. **Infinity and NaN.** A `real` may be an infinity or a NaN (§3.5.7), and so may a part of a `complex` (§3.5.15). Every operation takes one and answers IEEE 754's value, as Python's operators do: `math.inf + 1` is `inf`, `math.inf - math.inf` is `nan`, `1 / math.inf` is `0.0`, and an operation with a NaN operand is `nan`, save where IEEE 754 answers a number (`math.nan ** 0` 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 at runtime when the operands are finite (`1 / 0.0`), as in Python. Python answers most of these and raises for some of the same kind; DKE Python answers each one, so it departs from Python where Python raises, and at three corners where Python's answer breaks its operator's own rule: | Expression | DKE Python | Python 3.11 | |---|---|---| | a `**` with a `real` operand and a result past the range: `10.0 ** 400` | `inf` | `OverflowError` | | `round(x, n)` past the range: `round(1.7976931348623157e308, -308)` | `inf` | `OverflowError` | | an infinite or NaN dividend over a zero divisor: `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` | `inf`, the floor of the quotient | `nan`, derived from `%` | | `-1 % math.inf` | `nan`: no remainder in `[0, inf)` leaves `-1` | `inf` | | `-1 // math.inf` | `-0.0`, the floor of `-0.0` | `-1.0` | A `complex` computes by Python's algorithms, a number beside it as the complex with that real part, so `complex(math.inf, 1) + 1` is `(inf+1j)` and `complex(math.inf, 0) * 2.0` is `(inf+nanj)`, as in Python. Where those algorithms carry a part of a product, a quotient or a whole power of finite operands past the largest `real` on the way, though the part itself is not past it, or lose it to an intermediate, the part is its exact value rounded once, `inf` or `-inf` only where the value is past the range: | Expression | DKE Python | Python 3.11 | |---|---|---| | `complex(1e200, 1e200) * complex(1e200, 1e200)` | `infj` | `(nan+infj)` | | `complex(1e308, 1e308) / complex(2, 2)` | `(5e+307+0j)` | `(inf+0j)` | | `complex(1e200, 1e200) ** 2` | `infj` | `(nan+nanj)` | | `complex(1e300, 0) ** 2` | `(inf+0j)` | `OverflowError` | | `complex(math.inf, 0) / 0j` | `(inf+nanj)`, C99 Annex G's quotient by zero | `ZeroDivisionError` | | `complex(-1, 0) ** 1e308` | `(nan+nanj)` | `ZeroDivisionError` | The last row is a power whose phase, the exponent times the base's angle, is past the largest `real`: no `real` holds it, so the direction of the answer is not one a pair of `real`s can state. Its answer is C99 Annex G's: `nan` in both parts for a modulus that is neither zero nor infinite, `inf` and `nan` for an infinite modulus, and zero for a zero one. Python reports this case as a zero divisor although the base is not zero. The departures of the `math` and `cmath` functions are §8.16's. **Powers, logarithms and moduli are correctly rounded.** A `**` with a `real` operand, and a `complex`'s `abs`, answer the `real` nearest the exact result. A `**` over a `complex` computes through powers, logarithms, exponentials, trigonometric functions and moduli, and each of those steps is rounded the same way. So each such answer depends on its operands alone, and is the same wherever it is computed. Python takes these functions from the platform it runs on. They are correctly rounded there almost always; where one is not, Python's last digit differs from DKE's. `abs(0.00023571114292694718-0.00029192858677257096j)` is `0.0003752093318068705`, the exact modulus rounded, where Python on a platform that rounds this one wrongly prints `0.00037520933180687044`. **A quotient of two `int`s is exact.** `/` over two `int`s answers their exact quotient rounded once to the nearest `real`, as Python's does: `9007199254740993 / 3` is `3002399751580331.0`, where dividing the two as `real`s, each already rounded, gives `3002399751580330.5`. **Floor division is exact.** A `//` with a `real` operand, and the quotient of `divmod`, answer the floor of the exact quotient, rounded once to the nearest `real`. Python floors a quotient it has already rounded, which is off in the last place where that rounding crosses an integer: `4.064465006478144e+17 // 27.2389457125187` is `1.4921521006630456e+16`, where Python answers `1.4921521006630458e+16`. The floor is a `real` at any size, so `-1.1036980736650453e+20 // -3.778510741988643e-30` is `2.920986994691367e+49`, and a zero dividend keeps the quotient's sign: `0.0 // -3.0` is `-0.0`. **An `int` beside a `real` is exact.** `+`, `-`, `*`, `/`, `//`, `%` and `divmod` with one `int` operand and one `real` answer the exact value of the operation, rounded once to the nearest `real`. Python converts the `int` to a `real` first, which rounds an `int` past `2**53`, and then rounds the result again: `9007199254740993 + 0.5` is `9007199254740994.0`, where Python answers `9007199254740992.0`, and `9007199254740993 % 1000.0` is `993.0`, where Python answers `992.0`. A remainder is the dividend less the divisor times the floor of their exact quotient, so a zero one takes the divisor's sign: `9007199254740993 % -3.0` is `-0.0`. An `int` a `real` holds exactly, and a `real` that is zero, infinite or NaN, answer as they did. `**` with an `int` past `2**53` and a `real` still converts the `int` first, as do the `math` functions given such an `int`. An integer expression **whose operands are all literals** is evaluated at compile time, in a range wider than `int`, and replaced by its result — but only when that result is itself in range. So `2 ** 63 - 1` is the `int` `9223372036854775807`, even though the intermediate `2 ** 63` is not representable: the range rule above governs the expression's **value**, not each step of a calculation the compiler did on the program's behalf. When the folded result is out of range the expression is left exactly as written and refuses at runtime, so an overflow stays catchable (§10.3). A division or modulo by zero is never folded, for the same reason: its refusal belongs to the run. A folded expression is also **free at the meter** — it states a value rather than asking for work, as a prefix `-` on a literal does by folding into it. Charging for `2 ** 63 - 1` while the same number written out in full costs nothing would price the legible spelling above the opaque one. `**` refuses in two further cases at runtime, and **neither of them is an overflow**. Both forms compile: the fault is in the values, not the shapes. A **zero base under a negative exponent** (`0 ** -1`, `0.0 ** -2`) is a division by zero written differently, and refuses at runtime as one — discriminator `division_by_zero`, carrying the same wording Python gives that route. A **negative base under a fractional exponent** (`x ** 0.5` with `x` a negative `real`) has no real value; Python answers it with a complex number, which the `real` this expression is typed as cannot hold — so it too refuses at runtime, with the discriminator `complex_result`. The computation is performed wherever the expression is typed `complex` (§3.5.15), which is two places. Written with a negative literal base and a fractional literal exponent, the power is typed `complex` and answers what Python answers: `(-8) ** 0.5` is `(1.7319121124709868e-16+2.8284271247461903j)`. And a `complex` operand makes any power a `complex`, so `complex(x) ** 0.5` computes what `x ** 0.5` refuses at runtime for a negative `x`. What is declined is the power of two operands typed as numbers and not both literals, since its type is fixed before its values are known. Neither restriction reaches the neighbouring shapes: a negative base under an **integral** exponent is ordinary (`(-8.0) ** 3.0` is `-512.0`), and so is a zero base under a **non-negative** one (`0 ** 0` is `1`, `0 ** 1` is `0`). ### 8.4 String concatenation The `+` operator concatenates **two strings**: | Form | Signature | |------|-----------| | `(string, string) → string` | string concatenation | A `+` with one `string` operand and one **non-string** operand is a **type error** — as in Python, which raises `TypeError`, DKE Python does **not** implicitly render the other side. Convert it with `str(…)`, which renders any expressible value to its text form: `"count = " + str(xs.length)`, `"flag = " + str(b)`, `"at = " + str(t.value)`. An **f-string** applies `str(…)` to each field automatically, so `f"count = {xs.length}"` is the idiomatic form (§3.5.10). **List concatenation.** With **two `list` operands** (and no `string`), `+` concatenates: `[1, 2] + [3, 4]` is `[1, 2, 3, 4]`, a new list. `xs += ys`, `xs.append(v)` and `xs.extend(ys)` change `xs` in place to the same concatenation (§8.14), and `xs += c` takes any finite collection `c`, as `extend` does, where `xs + c` takes a list alone: `xs = [1]` then `xs += (2, 3)` leaves `[1, 2, 3]`, and `xs + (2, 3)` is refused in Python's words, `can only concatenate list (not "tuple") to list`. The result element type follows the list-literal rule (§5.1): two lists of the same element type give that type (`list + list` → `list`), two lists of differing **scalar** element types box into `list` (`[1] + [3.5]` → `list`, holding numbers when both sides hold numbers, §5.6), and an empty operand takes the other side (`[] + [1]` → `list`); a non-scalar element mismatch is a type_error, the same kind-strictness list literals apply. A `list` combined with a non-`list`, non-`string` operand (`[1] + 5`) is a type_error. **Tuple concatenation.** With **two `tuple` operands**, `+` concatenates: `(1, 2) + (3,)` is `(1, 2, 3)`, a new immutable tuple. The result type is the two operands' **position lists joined**, not an element type unified — each position of a tuple keeps its own type, which a constant index reads (§3.5.11, §5.3), so its type is the sequence of its positions, and `tuple + tuple` is `tuple`. A constant index into the result therefore reads that position's own type: after `t = (1, "a") + (5,)`, `t[2]` is an `int` and `t[1]` a `string`. Both arities are known at compile time, so this is exact. The empty tuple takes the other side (`() + (1,)` → `tuple`). A `tuple` combined with a non-`tuple` operand (`(1,) + [2]`, `(1,) + 5`) is a type_error. A concatenation involving a **variadic** tuple — one whose length is not known until run time — has no static arity, so its result is a variadic tuple and needs one element type covering both sides. That type is unified by the rule list concatenation uses above: equal element types give that type, and differing **scalar** element types box into `value`. Element types that do not unify (a collection element beside a scalar one) are a type_error, because no static type describes an unknown-length run of one type followed by positions of another. Two fixed tuples never meet this restriction. ### 8.5 Field access See §5.2 and §5.4 for the full table of well-typed field accesses. Accessing a field not in the table is a type_error. ### 8.6 Indexing, slicing, and string methods See §5.3. Indexing selects one element; the index expression MUST be `int`, and an out-of-range index refuses at runtime (§10.3). A `string` may additionally be indexed to read a single character (a negative index counts from the end — `s[-1]` is the last character), **sliced** (`s[start:end:step]`, all three components optional; negative bounds count from the end and a negative step reverses, e.g. `s[::-1]`) to read a substring, and transformed by the string methods `s.find`, `s.rfind`, `s.index`/`s.rindex` (like `find`/`rfind` but refusing when the substring is absent), `s.count`, `s.startswith`, `s.endswith`, `s.replace`, `s.upper`, `s.lower`, the strip family `s.strip`/`s.lstrip`/`s.rstrip`, the character-class predicates `s.isspace`/`s.isdigit`/`s.isdecimal`/`s.isnumeric`/`s.isalpha`/`s.isalnum`/ `s.isupper`/`s.islower`/`s.istitle`/`s.isascii`/`s.isprintable`/`s.isidentifier` (each `() → bool`), the list-bridge methods `s.split` (→ `list`) and `sep.join` (an iterable of strings → `string`, §5.3), and the collection-returning methods `s.partition` / `s.rpartition` (→ `tuple`, splitting once at the first / last occurrence of the separator) and `s.splitlines` (→ `list`, splitting at line boundaries) (§5.3). A constructed `list` (§5.1) is indexed (`xs[i]`, negative from-end permitted) and sliced (`xs[a:b:c]`, yielding a new `list`) by the same rules. Slicing and every string method **except `split`/`partition`/`rpartition` and `index`/`rindex` and `translate`** are **total** — they never refuse: an out-of-range slice bound is clamped, an inverted range yields `""` (or the empty list), `find` returns `-1` when the substring is absent, `startswith`/`endswith` and the `is…` predicates return a `bool`, and case mapping applies the full mapping, which can lengthen a string (§5.3). `split`/`partition`/`rpartition` refuse only on an **explicit empty separator** (`empty_separator`, §10.3), `index`/`rindex` refuse when the substring is absent (`substring_not_found`), and a slice **step of `0`** is likewise a catchable refuse (§5.3). All positions count characters (a string) or elements (a list) (§5.3). **The number methods.** Every number and a `complex` take `x.conjugate()`, with no argument. A `complex`'s conjugate is the value with its imaginary part's sign changed, `(3+4j).conjugate()` is `(3-4j)`, and a number is its own: an `int` or a `bool` answers an `int`, so `True.conjugate()` is `1`, and a `real` answers itself. Their parts are fields, `.real` and `.imag` (§5.2). A receiver that is not a number is refused, and a claim's `value` is told to narrow first, as an operator tells it (§5.6). The rest take no argument either, each on the numbers Python gives it to, a `bool` as the `int` it is, and each is refused elsewhere as `.conjugate()` is: | Method | Taken by | Answers | |---|---|---| | `x.is_integer()` | `real` | `bool`: whether it has no fraction, `(2.0).is_integer()` is `True` | | `x.hex()` | `real` | `string`: its exact value in hexadecimal, `(1.5).hex()` is `0x1.8000000000000p+0` | | `x.as_integer_ratio()` | `int`, `bool`, `real` | `tuple`: its value in lowest terms, `(1.5).as_integer_ratio()` is `(3, 2)` | | `x.bit_length()` | `int`, `bool` | `int`: the bits its magnitude takes, `(255).bit_length()` is `8` | | `x.bit_count()` | `int`, `bool` | `int`: the one bits in its magnitude, `(7).bit_count()` is `3` | An `int` and a `bool` have two more fields: `.numerator`, the int itself, and `.denominator`, which is `1`. A ratio past the signed 64-bit range refuses with `arithmetic_overflow`, where Python answers a larger integer, and the denominator of a real as small as 0.00001 is already past it. An infinity refuses with `arithmetic_overflow` and a NaN with `value_error`, in Python's words, `cannot convert Infinity to integer ratio` and `cannot convert NaN to integer ratio`. `math.inf.is_integer()` is `False`, and `math.inf.hex()`, `(-math.inf).hex()` and `math.nan.hex()` are `inf`, `-inf` and `nan`, as in Python. **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, as with any built-in. | Call | Answers | |---|---| | `str.maketrans(x, y)` | `dict`: each character of the string `x` to the character at its place in the string `y`, as code points, the last mapping of a character given twice kept. A `value_error` where the two differ in length | | `str.maketrans(d)` | `dict`: `d` with each string key as its code point, its `int` and `bool` keys and every value as they are. A `value_error` where a string key is not one character long, and a `type_error` for a key of any other type | | `dict.fromkeys(xs, v)` | `dict`: each element of `xs` to the one value `v`, in the order of their first appearance | | `float.fromhex(s)` | `real`: what `s` spells in hexadecimal, `float.fromhex("0x1p3")` is `8.0`, and `inf`, `infinity` and `nan` as `float` reads them. A value past the largest `real` is `inf` or `-inf`, where Python raises `OverflowError` (§8.3, *Infinity and NaN*), and a `value_error` where `s` spells nothing | Python answers `str.maketrans(x, y, z)` and `dict.fromkeys(xs)` with `None` among the values, which has no spelling here, so each is a type_error that says what to write instead. Neither takes a keyword, as in Python, and a `*` among their arguments spreads as at any built-in method's call (§8.15): `str.maketrans(*pair)`. A value of the type reaches each of them too, as in Python: `"a".maketrans("b", "c")`, `d.fromkeys(xs, 0)` and `x.fromhex("0x1p3")` where `x` is a `real`. **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()`, `list.count([1, 1], 1)` is `2` and `int.bit_length(255)` is `8`. `T` is `str`, `int`, `bool`, `float`, `complex`, `list`, `dict`, `set` or `tuple`, and `m` any method that type has here. One that changes its receiver changes what is given first: `list.append(xs[0], 2)` is `xs[0].append(2)`, and as a value it is refused as that is, since the `None` it answers has no spelling here. A call with no receiver is a type_error in Python's words, `unbound method str.upper() needs an argument`; so is a receiver of another type, `descriptor 'upper' for 'str' objects doesn't apply to a 'int' object`, and a name the type does not hold, `type object 'str' has no attribute 'nope'`. A name the program binds, in a comprehension's `for` as well, is no longer that type, as with any built-in. **String templates.** A template holds text and fields, read in order as CPython's `str.format` reads them. `{{` and `}}` are a brace. A field is `{name!conversion:spec}`, each part after the name optional. `{}` takes the next positional argument, `{0}` the one at that position and `{a}` the keyword, and one template may not use both of the first two. After its first part, `.x` reads a field and `[w]` an item: a `w` all digits is a position in a `string`, a `list` or a `tuple` and a key of a `dict`, and any other `w` a string key. `!r`, `!s` and `!a` convert first, by `repr`, `str` and `ascii`. `:spec` is what `format(value, spec)` takes (§3.5.10), and it may hold fields of its own, one level deep. `.x` on an instance names a field the store holds, which is not read in the middle of an expression (§13.3), so it refuses as the program runs, and a name bound to the field reads it. Every fault in a template is a catchable refuse in CPython's words (§10.3): a `key_not_found` for a name no argument holds, an `index_out_of_bounds` for a position past the last, a `type_error` for a value that does not take what a field asks of it, and a `value_error` for the rest, as with `Single '{' encountered in format string`. **Dict and set methods, and the collection builtins.** A `dict` (§5.1) is viewed with `d.keys()` (→ `dict_keys`), `d.values()` (→ `dict_values`), and `d.items()` (→ `dict_items`), each walking the dict in insertion order as it stands when the view is read (§5.1), and read defensively with `d.get(key, default)` — the value for `key`, or `default` when the key is absent. A default is **required** (DKE Python has no `None` to return for the 1-argument form); the result type is the join of `V` and the default's type (the same unification `and` / `or` use, §8.2). `d.pop(k, default)`, `d.setdefault(k, default)` and `d.popitem()` read the dict and change it at once (§8.14). A `set` is combined with `s.union(t)`, `s.intersection(t)`, `s.difference(t)`, and `s.symmetric_difference(t)`, each returning a new sorted set, and compared by inclusion with `s.issubset(t)`, `s.issuperset(t)` and `s.isdisjoint(t)`, each returning a `bool`. The argument of all seven is any collection `set(t)` takes (§8.13) — a set, a list, a tuple, a string, a dict's keys, or a generator expression (§8.9). `union`, `intersection` and `difference` take any number of them, each read over what the ones before it left, and with none answer a copy of `s`, as in Python: `{1, 2, 3}.difference([1], [2])` is `{3}` and `{1}.union()` is `{1}`. For `union` and `symmetric_difference`, whose result holds elements of both, an element type other than `T` joins `T` as a list literal's elements join (§3.5.9): two scalar value classes give a `set` (§5.6), so `{1}.union(["a"])` is `{1, 'a'}`, and a tuple beside a scalar is a type_error, since no `value` holds one. The five that keep only what is in `s`, or answer a `bool`, take any element type, since an element of another type equals nothing in `s`: `{1}.issubset(["a"])` is `False`, `{1}.isdisjoint(["a"])` is `True` and `{1}.intersection(["a"])` is an empty `set`. The operators on two sets are the same relations: `s | t`, `s & t`, `s - t` and `s ^ t` are the four combinations, and `s <= t`, `s < t`, `s >= t` and `s > t` are subset, proper subset, superset and proper superset (§8.1). An operator takes two sets where the method takes any collection, as in Python. The augmented forms `s |= t`, `s &= t`, `s -= t` and `s ^= t` change `s` in place to the new set (§4.3), and so do the statements `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()`, while `s.pop()` removes an element and answers it (§8.14). One that would hold an element type `s` cannot is a type_error, as a name keeps one type (§6.4): after `s = {1}`, `s.add("a")` and `s |= {"a"}` are refused, as `xs.append("a")` is on a `list`, and a `set` takes any scalar. The operators follow the methods: `s - t` and `s & t` take a `t` of any element type, and `s | t` and `s ^ t` one whose elements join `s`'s as the methods' do. A difference holds `T`, and so does an intersection with a set of another element type; a union, intersection or symmetric difference of an `int` and a `real` element type holds a `value` holding a number, and a union or symmetric difference of two other scalar element types a `value` (§5.6). `xs.copy()`, `s.copy()` and `d.copy()` each answer a new collection of the receiver's kind holding its elements — what `list(xs)`, `set(s)` and `dict(d)` build (§8.13) — and take no argument. It is a list, a set and a dict that take it; on any other type it is a type_error naming the three. An `int`, a `real` and a `bool` of equal value are one element to every relation, compared exactly (§8.1). Where both sides hold such a pair, the class that survives is Python's: a union keeps the left operand's element, a difference the left's, and an intersection the element it walks — the argument's when that is not a set, otherwise the smaller set's, and the right operand's when the two are the same size (`{1, 2.5} & {1.0}` is `{1.0}`). ``` # set-relations.dpy — compare two sets by inclusion, and combine them four ways. def set_relations(): staff = {"ana", "bo", "cy"} on_call = {"bo", "dee"} print(on_call <= staff, {"bo"} < on_call, staff.isdisjoint(["eve"])) print(staff | on_call, staff & on_call, staff - on_call, staff ^ on_call) seen = set([]) seen |= on_call seen -= {"dee"} print(seen, staff.issuperset(seen), {1, 2.5} == {1.0, 2.5}) ``` That prints `False True True`, then `{'ana', 'bo', 'cy', 'dee'} {'bo'} {'ana', 'cy'} {'ana', 'cy', 'dee'}`, then `{'bo'} True True`. Two builtin functions pair collections into tuples, and a third reads one backwards. Each returns an **iterator**, as CPython's do. `zip(a, b, …)` → `iterator>` pairs elements position by position up to the shortest input, and `zip()` with no input gives nothing. With `strict=True` it pairs them the same way, and inputs that are not all the same length end in a catchable refusal, as CPython's `zip` raises its `ValueError`. The refusal names the input that ended early or late against those before it: `zip() argument 2 is shorter than argument 1`, `zip() argument 3 is longer than arguments 1-2`. `strict` is read for its truth value, as Python reads it, and the pairs read before the refusal have been given. `enumerate(xs [, start])` → `iterator>` numbers each element, from `0` by default or from an optional `int` `start` (given positionally or as the `start=` keyword; `start` may be negative). Each argument to `zip`/`enumerate` is any finite collection (a `dict` contributes its keys, a `set` its sorted elements, a dict view what it views) or an iterator. **`reversed(xs)`** → `iterator` reads `xs` in the reverse of the order `for` walks it, so a `dict` contributes its keys, a `set` its sorted elements and a dict view what it views, backwards. It accepts a set, which CPython refuses, because this language orders every set (§3.5.13). An argument that is not a finite collection — an iterator included, which CPython refuses too, since an iterator is read only forwards — or an argument count other than one is a type_error. An iterator is **read once**. What reads it — a `for`, a comprehension, `list`, `tuple`, `set`, `dict`, `sorted`, `sum`, `min`, `max`, `any`, `all`, a string's `join`, a list's `extend`, a set's or a dict's `update`, another `zip` or `enumerate`, `next`, which reads one, or a target list it is unpacked into (§8.11) — takes each element it gives, and that element is gone. So a second read finds only what the first left: after `e = enumerate([5])`, `list(e)` is `[(0, 5)]` and a second `list(e)` is `[]`. A `for` reads one element per pass, so a `break` leaves the rest for the next reader, and `x in e` reads up to and including the first element equal to `x`. `any` and `all` read up to the element that decides, `s.intersection(e)` until it has found as many elements as `s` holds, `s.issuperset(e)` up to one `s` lacks, and `s.isdisjoint(e)` up to one `s` holds, as CPython's do. Every name holding an iterator holds the same one, so reading it through one name uses it up for all of them. An iterator reads its source as the source stands when each element is read, as CPython's does, and never past the length the source had when the iterator was made, which is the bound a `for` keeps (§6.3). A list that shrinks is seen shrinking: `reversed` over a list whose last element has since been removed ends at once, as CPython's does. A dict or a set whose size changes refuses the next read with CPython's `RuntimeError` text, `dictionary changed size during iteration` or `Set changed size during iteration`, which `except refuse` catches, and refuses every read after it the same way. A set's `update` and `difference_update` and a dict's `update` read an iterator one element at a time, changing the collection by each before the next read (§8.14), so one over the collection they change is refused there. A source that has ended stays ended. An iterator has **no length and no position**. `len(e)`, `e[0]` and `e[1:]` are type_errors in CPython's words — `object of type 'zip' has no len()`, `'zip' object is not subscriptable` — each naming `list(...)`, which reads the iterator into a list that has both. An iterator is always **truthy** (§8.2), used up or not, and is equal only to itself (§8.1). It **prints as its class** — ``, ``, and for `reversed` the class CPython gives its argument: `` over a list, `` over a dict or a keys view, `` over a values view, `` over an items view, and `` over anything else. CPython prints the object's address as well, which differs from run to run; a DKE Python render is deterministic and leaves it out, as an instance's does (§13.2). Printing an iterator reads nothing from it. An annotation writes its type `iterator` (§5.5). ``` names = ["ana", "bo"] pairs = zip(names, [90, 85]) print(pairs) # print(list(pairs)) # [('ana', 90), ('bo', 85)] print(list(pairs)) # [] for i, name in enumerate(names, 1): print(i, name) # 1 ana, then 2 bo print("".join(reversed("abc"))) # cba scores = dict(zip(names, [90, 85])) ``` **`iter(x)`** → `iterator` makes an iterator over `x`, which is any finite collection or an iterator. A collection is read in the order `for` walks it, so a `dict` gives its keys, a `set` its sorted elements, a dict view what it views and a `string` its characters, by the rules above. An iterator is answered with itself, as CPython answers it: after `e = iter(xs)`, `iter(e)` is `e`, and reads on from where `e` stands. The iterator prints as the class CPython gives it: `` over a list, `` over a tuple, `` over a set, `` over a dict or a keys view, `` over a values view, `` over an items view, and over a string `` when each of its characters is ASCII and `` when one is not. A range is a list here, so `iter(range(3))` prints `` where CPython's is a `range_iterator`, and a collection read from the store prints ``, the class CPython gives an iterator over a sequence that names none of its own. A compile-time message names a string's iterator `iterator`, since its class depends on characters a program holds only when it runs. `iter` takes exactly one argument. CPython's two-argument form calls a function until it answers a sentinel, and a function is not a value here (§8.8), so `iter(f, s)` is a type_error. So are `iter()` and an argument that is not a collection: `iter(5)` names the collections `iter` takes. **`next(e [, default])`** reads the next element of the iterator `e`, which is then gone from `e`, as it is for every reader above. When `e` has none left, `next(e, default)` answers `default`, and `next(e)` refuses with discriminator `stop_iteration`, which `except refuse` catches. CPython raises `StopIteration` there, and this language catches by category rather than by exception class (§9.2). The default is evaluated before `e` is read, whether or not it is answered, as CPython evaluates a call's arguments. The default and the elements join as two elements of one list display do (§5.1), so a default of another scalar class gives a `value` (§5.6): `next(iter([1, 2]), "done")` is a `value`. A default that cannot join, such as a list beside an `int`, is a type_error. An `e` that is not an iterator is a type_error in CPython's words: `next(xs)` over a list is `'list' object is not an iterator`, and the message names `iter(...)`, which makes one. When the source refuses the read, because a dict or a set changed size or a strict `zip`'s sources differ, `next` refuses as every reader does, and the default does not answer in its place. `next` takes one or two arguments and no keyword argument, as CPython's does. It may stand alone as a statement, because reading changes `e`: `next(rows)` skips an element (§4.3). Each call reads at most one element, so `next` adds no loop to the language (§9.5). ``` e = iter([1, 2]) print(e) # print(next(e), next(e)) # 1 2 print(next(e, "done")) # done try: next(e) except refuse as r: print(r.discriminator) # stop_iteration ``` **Dict views.** A dict's `.keys()`, `.values()` and `.items()` answer a view (§5.1), which differs from an iterator in two ways: it is read as often as it is read, each time from the dict as it then stands, and it has a length. So `len(d.keys())` is the dict's size and `list(d.items())` can be called twice. A keys or an items view takes the set operators beside any collection, on either side, and the inclusion order beside a set or another such view, and each combination answers a set: `{"a": 1, "b": 2}.keys() & {"a", "z"}` and `{"a": 1, "b": 2}.keys() & ["a", "z"]` are `{'a'}`, and `{"a": 1, "b": 2}.keys() <= {"a", "b", "c"}` is `True`. The collection is read as `set(...)` reads it (§8.13), so a list among its elements is a type_error, and a number beside a view is one in CPython's words, `'int' object is not iterable`. Of two equal numbers of two classes, an intersection keeps the one CPython's keeps: the collection's, unless it is a set no smaller than the view, so `{1: 0}.keys() & [1.0]` is `{1.0}` and `{1: 0}.keys() & {1.0}` is `{1}`. Of the seven set methods a keys or an items view has `isdisjoint` alone, which takes any collection, as CPython's does, and a values view has none. A view is read into a list, a tuple or a set by the conversions (§8.13), and `dict(d.items())` is a copy of `d`. A view has no `.mapping`, the attribute CPython's has for the dict behind it: the dict is the one the program read the view from. **`range(stop)`**, **`range(start, stop)`** and **`range(start, stop, step)`** return the `list` of the integers from `start` (default `0`) up to but not including `stop`, `step` apart (default `1`). A negative `step` counts down, toward a `stop` below `start`. A range whose `stop` is not past `start` in the direction of `step` is empty: `range(0)`, `range(5, 2)` and `range(0, 5, -1)` are all `[]`. Every argument is an `int`; a `bool`, a `real` or any other type is a type_error, and so is a call with no argument or with more than three. A zero `step` refuses at run time with CPython's `ValueError` text, `range() arg 3 must not be zero`, and discriminator `value_error`, which `except refuse` catches. **Unlike CPython's, the result is an ordinary `list`**, so it composes with every list read (`len(range(3, 30, 4))` is `7`, `range(10)[2:8:2]` is `[2, 4, 6]`). It prints as the list of its elements, so `print(range(3))` prints `[0, 1, 2]` where CPython prints `range(0, 3)`, and it is equal to that list. `reversed` reads it as the list it is, so `reversed(range(3))` prints `` where CPython's is a `range_iterator`. It is also built in full, so its elements count toward any limit a service sets on how much one run builds, exactly as the elements of `[0] * n` do. A range past such a limit is refused, and `except refuse` does not catch it. Three more read a whole iterable. **`sorted(iterable [, key=] [, reverse=])`** returns a **new** `list` of the elements in ascending order — descending when `reverse` is `True` (a stable sort; `key=` and `reverse=` are keyword-only, in either order) — ordered by the elements themselves or, given a `key=`, by their keys, as `min` and `max` are (§8.3), so that only the keys need be orderable and elements of equal key keep their order in both directions: `sorted(words, key=len, reverse=True)` is longest first, equal lengths in the order they came. Without a key the elements must be orderable: numeric, `scientific`, `string`, `datetime`, `duration`, or a nested list/tuple whose own elements are (recursively) orderable, in which case the sort recurses lexicographically (§8.1). An `int` and a `real` sort by value and keep their classes (`sorted([3, 2.5])` is `[2.5, 3]`); two of equal value keep their order, the sort being stable. A keyword `sorted` does not take is a type_error in Python's words, the first one written, and is named for `sort()`, which Python's `sorted` passes it to: `sorted(xs, x=1)` is `'x' is an invalid keyword argument for sort()`. So is one a list's `sort` does not take, after its positional arguments are counted (§8.14). **`any(iterable)`** and **`all(iterable)`** fold **truthiness** (§8.2) over the elements to a `bool`: `any` is `True` when at least one element is truthy (`False` for an empty iterable), `all` is `True` when every element is truthy (`True` for an empty iterable). Each of the three reads an iterator as readily as a collection. ### 8.7 The `print` statement ``` print() ``` Appends output to the script's transcript. `print` is written as a call — `print("temp=" + str(t.value))`. Evaluates `` and appends its rendering. An argument written with `*` is a spread, and each of its elements is rendered as an argument of its own (§8.15). The argument type MUST be a type for which a textual rendering is defined: `string`, `int`, `bool`, a `value` (§5.6, rendered by its class), a constructed `list`, a class instance (§13.2), or any discriminator type. A constructed `list` renders as `[e1, e2, …]` — the elements comma-separated in brackets, each rendered recursively (a nested list renders in full, e.g. `[[1, 2], [3]]`; an empty list is `[]`). A **string element inside any collection** is rendered as its `repr` — single-quoted, switching to `"` only when the value contains a `'` and no `"`, with `\`, the quote, and `\n`/`\r`/`\t` escaped (e.g. `['a', 'b']`, byte-matching CPython `str([...])`) — so a nested string is unambiguous, while a **bare top-level string** printed on its own stays unquoted (`print("a")` → `a`). A constructed **tuple** renders `(e1, e2, …)` (a one-element tuple `(e,)`); a **dict** renders `{k1: v1, k2: v2}` in insertion order; a **set** renders `{e1, e2, …}` in sorted order (the empty set, only reachable as a computed result, renders `set()`) — each element rendered recursively. A **class instance** renders as its class and its subject, `` (§13.2). Written without its parentheses, as Python 2 wrote it, `print x` is a parse_error wherever an expression is read, as CPython reads it: a call missing its parentheses, answered at the word `print` (§10.1). The same holds for `exec`, a builtin this language leaves out. A call of the same shape among the values after the word is answered first, as CPython reads them first. A `print` followed by a `(` or `not in` begins an expression, and is not this error. A `[` after the word begins an index of it, as CPython reads `print [a]`; where a grammar error fails the line past the word and the `[` opens a list that reads, CPython's rule for a Python 2 statement reads that list as the call's first argument, so `print [a], b +` and `print [] +` are this error, answered at the word. A grammar error that rule raises among the values it reads is the answer instead, as `print [b for x in y], (c=d) = 1` is a parse_error at column 24, and inside a bracket a value straight after the list is a forgotten comma, answered at the list, as `[print [] b]` is at column 8. A list among the targets before an `=` is read as a target, and is not this error. An `await` and the value after it are the word's first argument, as CPython reads them, so `print await x` is this error, answered at the word. A `~` stands only before a value, so `print ~x` is this error, answered at `print`. A `-` or `+` after the word begins an expression CPython compiles, so `print -a` subtracts and `print -a, b` is a tuple; where a value stands side by side after that expression, outside a bracket, CPython's first parse fails and its rule for a Python 2 statement then reads `-a` as the call's first argument, so `print -a b` and `print -a, b c` are this error, answered at the word. A `*` after the word is read as a sign is, so `print *a b` is this error too, answered at the word. Inside a bracket the two values are the answer, as a forgotten comma is, from the word that begins the first of them. Inside a bracket an `=` after the word's values makes no target of them, a keyword argument's name included, so `f(print [a] = 1)`, `f(print -a = 1)` and `[print [a] = 1]` are this error, answered at the word. The grammar error that fails the source past the word may stand on a later line: CPython's first reading of the source fails there, and its second, with its error rules on, meets the word first, so the two lines `print [a]` and `b +` are this error at line 1, and so are `print -a` and `b +`. A decorator's expression is read as any other, so the three lines `@print [a]`, `def f(): pass` and `b +` are this error at line 1, column 2, at the word. An error CPython raises on its first reading is the answer instead, at its own line: a bracket never closed, a string not terminated, a number its tokenizer refuses, an error in an f-string's field, and a dict's own error at a key that reads, so the two lines `print [a]` and `x = {1: 2, a}` are a parse_error at line 2, column 12. A character no token begins with is not one of them: CPython's parser meets it as a token and raises nothing there, so the two lines `print [a]` and `x = $` are this error at line 1. So is the later error where the word stands among the targets a `del`, a `for` or an `as` names, outside a bracket of their own, which CPython reads as no expression: the two lines `del print [a]` and `b +` are a parse_error at line 2. **The arguments are read as CPython reads them.** The values after the word are a comma-separated list, and after each one the value side by side with it, where one stands there, is read whole, so a bracket that reading opens and the source never closes is the answer (the bracket rule, below): each of `print 's', y (`, `print y, z d (` and `print x, 't' 'u' (` is a parse_error, the bracket never closed, while `print y, z d`, whose reading stops short of the end, is answered at the word. A starred or awaited value among them is read as any other, so `print x, *a(1` is a parse_error at column 12, the bracket never closed. After a list that is the word's first argument, the values that follow it are read as the call's arguments, so `print [a], c(1` is a parse_error at column 13, never closed, where `print [a] c, d(1`, whose reading stops at the `c`, is answered at the word. Inside a bracket the two values side by side are the answer, a forgotten comma at the first, as CPython answers them before it weighs the word: `f(print e < b d e` is a parse_error at column 9, where `print e < b d e` alone is one at column 1. After a first argument that is a conditional or a `lambda` nothing is read on, as CPython's rule for a forgotten comma has read that argument once already with its error rules off and keeps the reading: `print e if b else a d (` is answered at the word, where `print x, e if b else a d (`, with the conditional second, is never closed. That rule is skipped where a string follows the word, and reads the word as an operand where a sign does, so `print 's' if b else a d (` and `print -a if b else c d (` are never closed, at columns 25 and 24. A first argument's call whose arguments stand in an order CPython's grammar does not read is read to the first out of place, for the same reason, so `print a(c=1, 1` is answered at the word, where `print x, a(c=1, 1`, with the call second, is never closed, and so is `print -a(c=1, 1`, whose word that rule reads as an operand. Where that rule read no first argument, after a string, a list, a sign or a `*`, what Python raises inside a call or a subscript that trails the first argument stands: `print [a](a b, 1` is a parse_error at column 11, two values side by side, and so are `print -x(a b, 1` at column 10 and `print [a][a b, 1` at column 11. `print x(a b, 1`, whose first argument that rule read, is answered at the word, and so is `print [a](, 1`, whose call fails where no rule raises. In a statement that begins with an element and a comma, the elements are read on, with or without a comma between them, to a call of this shape, which is answered at its word with this error: `y, a b print 's' d 't' (` is a parse_error at column 8. The **store-derived** finite collections and `proof_tree` each have a rendering — `str(x)` is total over "any expressible value" (§8.13). **`print` accepts any expressible value**, and the renderings of the store-derived types are stated in §8.13. Reaching for a `string`-shaped field instead of printing the value is correct too, and the better habit for anything you intend to parse. ### 8.8 No I/O, no closures, no first-class functions DKE Python has no I/O surface other than (i) verb calls against the store and (ii) `print`. It has no closures, no anonymous functions, no first-class function values, and no captured environments. A script is reached by its name: through a plain call (§4.3, §9.7), or named as the `key=` of `sorted`, `min` or `max` (§8.3), where the name resolves as a call's does. `lambda` is a parse_error: a lambda is a function held as a value, and no type (§5) holds a function. Where Python reads no `lambda`, as an operator's operand, the parse_error is at the word, with a header or without one, so `y, not lambda: e` is one at column 8. The word is reserved (§3.7), as it is in Python, so it names nothing either: where it begins a lambda whose header reads no parameter list, as an assigned name, a target, an argument or a path's head does, the parse_error is where Python's reading of that header stops, so `lambda = 5` is one at column 8 and `current(lambda.s.a)` one at column 15; as a `del`'s target or a `with`'s name it is one at the word. A call names what it calls, so `(…)` after any other value — a literal, or what an index, a call or a `.name` answers, as in `xs[0](1)` or `f()(1)` — calls that value, which CPython reads and fails on as it runs, and is a type_error here in its words, `'int' object is not callable`, or as the program runs where no type is known for it. What only this language has is told that no value holds a function, and so is a change through such a call, `f()(1).pop()`, as a parse_error, and the name of a `def`, a method or a module read with no call, as a type_error. Its arguments, and what follows them, are read as CPython reads a call's, so a grammar error among them is the answer, as it is in a call of a method no value has: `x = {a}(, 1` and `x = a.b(, 1` are parse_errors at column 9. `None`, which this language does not have, is read there as the value Python reads, so `x = None(, 1` is a parse_error at column 10. Where that reading reaches the end of the source inside a bracket never closed, the bracket is the answer (§10.1), two values side by side among the arguments read to the token after the second, so `x = [a](a b` is a parse_error at column 8, never closed, and so is the value of an `=` that CPython reads as a named expression, as `if a = b(1)(` is at column 12. It has no generators and no `yield`; a generator expression is read only where it cannot be told apart from a comprehension (§8.9). Nor has it coroutines: an `await` with a value straight after it, which Python reads as its keyword, is a parse_error at the `await`, as `y = await b` is at column 5, and one before a `not`, a `~` or a `lambda`, none of which begins the primary Python reads after it, is a parse_error at that word. After a `print` written without its parentheses, the call is the answer instead (§8.7). Nor has it `async`: an `async` before `def`, `for` or `with`, or before a comprehension's `for`, is a parse_error at the `async`, after what follows it has been read as CPython reads the statement or the clause, so a grammar error inside is the answer — `async def f() d` is a parse_error at column 15, and `x = [e async for 1 in xs]` one at column 18. Both words are reserved (§3.7), as they are in Python, so they name nothing either: `await = 1` and `async = 1` are parse_errors at column 7, where Python's reading of them stops, and `x = await` is one at column 10. ### 8.9 Comprehensions A **comprehension** builds a constructed collection by bounded iteration in expression position. There are three forms — a **list** comprehension (`[…]`), a **set** comprehension (`{…}`, no colon), and a **dict** comprehension (`{… : …}`): ``` comp-clause := 'for' comp-target ( ',' comp-target )* ','? 'in' iter-expr ( 'if' cond-expr )* comp-target := name-target | '*' name-target # THE single starred target (<= one per list), a name or a nesting name-target := IDENT | '(' comp-target ( ',' comp-target )* ','? ')' # a name, or a nesting of names, which may hold its own star; an element target needs a `for` statement (§8.11) | '[' comp-target ( ',' comp-target )* ','? ']' # a nesting in brackets | '(' ')' | '[' ']' # a nesting of no names; each element must hold nothing iter-expr := or-expr # a conditional expression needs parentheses here list-comprehension := '[' expr comp-clause+ ']' set-comprehension := '{' expr comp-clause+ '}' dict-comprehension := '{' expr ':' expr comp-clause+ '}' generator-argument := expr comp-clause+ # bare: the only argument of a call that reads it whole | '(' expr comp-clause+ ')' # parenthesized: an argument of such a call ``` Each form has one or more `for` **clauses**. A single-clause comprehension iterates the finite `` (any finite collection type, a constructed `list`/`set`/`dict`, a `string` — by character, §6.3 — or an iterator, §8.6), binds each element to `` — or, when the clause names a **target list** (`for a, b in …`), destructures each element into those names (§8.11) — evaluates the optional `` (tested for **truthiness**, §8.2 — any value), and, for each element the condition admits, contributes a value to a new collection. A **list** comprehension appends `` → `list` (`T` the type of ``); a **set** comprehension collects `` → `set`, removing duplicates and sorting (the `` must be hashable — a scalar or a tuple of hashables); a **dict** comprehension maps ` : ` → `dict` in insertion order, last value winning on a repeated key (`` must be hashable the same way). `` (and ``) MUST produce a value; a statement-position verb is a type_error. Over a source whose element type nothing has supplied, a comprehension produces nothing, and a filter, ``, `` or `` that reads its loop variable is not checked (§6.3). A part skipped this way leaves its place in the type unknown, so `[x.upper() for x in []]` is `list`. A list or set comprehension's `` is one expression, so a tuple there MUST be written in its parentheses: `[(a, b) for b in xs]`. Without them, `[a, b for b in xs]` is a parse_error, as it is in CPython, where the element begins, and its message names the parentheses the element needs. Where such a comprehension holds another, among its elements or in what its clauses read, the inner one is answered. CPython reads such a comprehension wherever its reading of the statement reaches it, and answers it there before it weighs any target, so `f([a, b for b in xs]) = 1` and `f() = [a, b for b in xs]` are answered at the element rather than at the call. A comprehension whose clause names a target that cannot be assigned to is answered at that target in the same way: `del [x for 1 in xs]` is a parse_error at the `1`. A comprehension with **more than one `for` clause** is the flat **cartesian product**: the first clause is the outermost loop, each following clause nests inside the previous, and `` (and a dict's ``) is evaluated only in the innermost scope. A later clause's `` may reference the loop variables bound by earlier clauses (`[y for row in grid for y in row]` flattens `grid`). A clause may carry **multiple `if` filters** (`for x in xs if p if q`), which apply conjunctively. Each is asked only for its truth value, so their types need not join as the operands of an `and` must (§8.2): `[1 for b in [1] if 1 if [1]]` is `[1]`. The loop variable `` of each clause is **comprehension-local**: it is in scope within the clauses to its right, the ``s, `` (and a dict comprehension's ``), may shadow an outer or earlier binding of the same name, and does not leak to the enclosing scope. A list comprehension whose `` is itself a comprehension nests naturally (`[[y for y in row] for row in grid]` is a `list>`). A comprehension is bounded iteration over a finite source, so it terminates (§9.5) and, like a list literal, is evaluated as the program runs and stores nothing. ``` evens = [n for n in nums if n % 2 == 0] # list → list uniq = {n % 2 for n in nums} # set → set (deduped, sorted) squares = {n: n * n for n in nums} # dict → dict pairs = [a + b for a in xs for b in ys] # cartesian product (multi-clause) flat = [y for row in grid for y in row] # inner source sees the outer var tags = get(K.s.tags) # a read is bound first (§9.1)… labels = [c.value for c in tags] # …then iterated → list shouted = [w.upper() for w in text.split(" ")] # compose with split → list ``` The `` is any expression of a finite type; a store read (a verb call) is bound to a name first (as `tags` above) rather than written inline, consistent with §9.1. A **generator expression** is a comprehension's result and clauses written in a call's parentheses instead of brackets: `sum(x * x for x in xs)`. DKE Python has no generators, so it reads one only where nothing can tell it apart from the comprehension it spells, which is as the whole argument of a call that reads every element of that argument before it returns. Those calls are `sum`, `min`, `max`, `any`, `all`, `sorted`, `list`, `tuple`, `set`, `dict` (over pairs), a string's `join`, a set's `union`, `intersection`, `difference`, `symmetric_difference`, `issubset`, `issuperset` and `isdisjoint`, and the statements that change a collection by what they read (§8.14): a list's `extend`, a set's `update`, `difference_update`, `intersection_update` and `symmetric_difference_update`, and a dict's `update` (over pairs). There a generator expression is the list comprehension a reader would write in brackets, with the same element type, order, duplicates and value: `sum(x for x in xs)` is `sum([x for x in xs])`. A `*` reads every element of what it spreads before anything else goes on, so a generator expression in its own parentheses right after one, in a call or a display, is read the same way: `print(*(x for x in xs))` and `[*(x for x in xs)]`. A set's method taking several arguments reads each of them so: `s.union([1], (x for x in xs))`. Written bare, a generator expression MUST be its call's only argument, with no trailing comma. Beside another argument it MUST be parenthesized: `sorted((x for x in xs), reverse=True)`, `max((x for x in xs), default=0)`. A bare one beside another argument or before a trailing comma (`sum(x for x in xs, 1)`, `sum(x for x in xs,)`) is a parse_error, as it is in CPython. That holds in a call of any kind, not only one that reads its argument whole, and it is a grammar error, answered ahead of a refusal this language makes: `print(1, x for x in xs)` is a parse_error at column 10, where the generator expression begins. Where CPython's rule for it reads the arguments after the generator to the end of the source, inside the call's bracket never closed, the bracket is the answer (§10.1), as `x = a(a for a in b, 1` is a parse_error at column 6. A comma straight after another begins no argument, so an empty place after a keyword argument is no argument out of order, and the second comma is answered: `x = a(c=1,, 1` is a parse_error at column 11. Two of those calls read differently from a list, and they follow CPython. `any` stops at the first element that is truthy and `all` at the first that is not, so no element after it is evaluated: `any(1 // x > 0 for x in [1, 0])` is `True`, while `any([1 // x > 0 for x in [1, 0]])` refuses at runtime on the division by zero. And `sorted`'s `reverse=` and the `default=` of `min` or `max` are evaluated after the generator expression's first source and before its elements, which a program can observe only when both call a script that prints. Three more read it one element at a time, as CPython's do: a set's `update` and `difference_update` and a dict's `update` change the collection by each element before they read the next (§8.14). A generator expression that reads the collection being changed can tell this from its list comprehension: one over it finds it changed, so `s.update(x + 1 for x in s)` is refused with `Set changed size during iteration`, where `s.update([x + 1 for x in s])` adds every element, and one reading its length reads it as each element lands, so `s = {1}`, `s.update(len(s) + x for x in [10, 20])` leaves `{1, 11, 22}`. Anywhere else a generator expression would be a **generator object**, a one-pass iterator that CPython prints as ``, so it is a parse_error whose message names the list comprehension. That covers a generator expression bound to a name (`g = (x for x in xs)`), returned, held in a collection, used as an operand or a subscript base, iterated by `for`, or passed to any other call: `print`, which in CPython prints the object; `str`, `len`, `reversed` and `bool`, which would show it, refuse it or only test it; `enumerate` and `zip`, which read it one element at a time between other work; a method or verb not listed above; and a script, whose parameter would receive the object. Write the list comprehension there: `print([x for x in xs])`. ``` nums = [3, 1, 4, 1, 5] print(sum(n * n for n in nums)) # 52 print(any(n > 3 for n in nums)) # True print(sorted((n % 3 for n in nums), reverse=True)) # [2, 1, 1, 1, 0] print(", ".join(str(n) for n in nums)) # 3, 1, 4, 1, 5 ``` ### 8.10 Conditional expression A conditional expression `a if c else b` yields `a` when the condition `c` is **truthy** and `b` otherwise. The condition `c` may be any value (tested for truthiness, §8.2). The two branches must have the same type — which is the type of the whole expression — be two numbers, which join (§3.5.7), or be two class instances, which join under their common base as a list display's elements do (§13.8), or be two scalars that are not both numbers, or a scalar beside a `value`, which join to a `value` holding their classes as a list display's elements do (§5.6); any other mismatched pair (e.g. a `list` and an `int`) is a type_error. The first branch reads a name the condition narrows at its narrower class, and the second reads it at what the condition failing leaves it (§5.6). An `int` / `real` pair is typed `real`, and the expression yields the branch it selects, unconverted, as Python's does: `2.5 if False else 1` is `1`. That is a join of the types, not a promotion. ``` grade = "pass" if score >= 60 else "fail" step = n // 2 if n > 0 else 0 ``` A conditional expression with no `else` is a parse_error, answered where the expression begins, as CPython answers it: `y = x + 1 if a` at the `x`. One whose condition a `:` follows is answered at the `:`. A `:=` is no `:`, so `y = a if b := 1` is answered at the `a`. For the rule of §10.1 that a bracket never closed is the answer over a grammar error past its line, this one stands at the token that follows its condition. A condition refused part way, after a value it begins with, is answered the same way, since CPython reads the longest condition it can and then finds no `else`: `y = a if b +` and `y = a if b + else 1` are answered at the `a`. A refusal CPython words itself, one inside an f-string, and a bracket never closed are the answer in its place, and a string the condition holds is read before it: `y = a if b + "\x4"` is answered at the string (§10.1). A condition that ends in a call whose arguments do not parse is read to the value before the call, as Python backs off a trailer that fails, and finds no `else` after it: `x = a if f(, 1` is answered at the `a`, column 5. Where Python's reading of the arguments reaches the end of the source inside the bracket, the bracket is the answer, as `x = a if f(1` is at column 11. Evaluation is **short-circuit**: `c` is evaluated first, then **only** the selected branch — the unselected branch never runs, so a branch that would refuse (a division by zero, an out-of-range index) is not reached when it is not chosen. The conditional expression is right-associative and binds lower than every operator (§4.6). It is terminating by construction (a single selection, no iteration). ### 8.11 Unpacking A **target list** — two or more targets separated by commas, or one target followed by a comma, on the left of an `=` or between `for` and `in` — destructures a **tuple**, a **list** or an **iterator** into its targets. A target is a name, or an element or a slice of a collection (below): ``` a, b = (1, "two") # a = 1, b = "two" a, b = b, a # swap — the RHS may be a bare comma-list x, y, z = 10, 20, 30 # (an implicit tuple), parentheses optional k, v = "port=80".split("=") # a LIST — k = "port", v = "80" (a, b), c = ((1, 2), 3) # NESTED — a = 1, b = 2, c = 3 [a, b] = [1, 2] # in BRACKETS — the same target list p, (q, r) = 1, (2, 3) # nested target anywhere in the list a, *rest = (1, 2, 3, 4) # STARRED — a = 1, rest = [2, 3, 4] first, *mid, last = [1, 2, 3, 4] # star absorbs the middle: mid = [2, 3] p, (q, *r) = 1, (2, 3, 4) # a nested group holds its own star: r = [3, 4] xs[0], xs[1] = xs[1], xs[0] # ELEMENT targets — swap two elements i, xs[i] = 1, 5 # left to right: xs[1] = 5 for i, x in enumerate(xs): # i = index, x = element print(str(i) + ": " + str(x)) for k, v in inventory.items(): # k = key, v = value print(k + " = " + str(v)) ``` - **Assignment unpacking** (`a, b = `) takes a right-hand side that is a `tuple` whose arity **exactly** matches the number of targets, or a `list` (below). The RHS may be written as a **parenthesized tuple** (`(1, 2)`) or as a **bare, unparenthesized comma-list** (`b, a`) — the two are equivalent, so the swap idiom `a, b = b, a` works. The whole right-hand side is evaluated **before** any target is bound, so a swap reads each name's prior value. Each target binds the tuple's value at its position (with that position's type). A tuple of a different arity (too few OR too many values) is a type_error, because a tuple's arity is in its type. - **A `list`**, a **variadic `tuple`** (§8.13), a **`string`**, a **`set`**, a **`dict`**, a dict's view or an **iterator** (§8.6) unpacks the same way, and each target takes the element type: `k, v = line.split("=")`, `x, y = args` for a `*args` parameter, `i, j = zip(xs, ys)`, `a, b = "xy"`, and `k, j = d`, which takes the dict's keys. No length of these is in the type, so it is checked when the statement **runs**, before any target binds. An iterator is read as far as the check needs, as in CPython: one element past the targets, or to its end when a target is starred. One of a different length is a **catchable refusal** (§10.3) with discriminator `value_error`, in CPython's words — `not enough values to unpack (expected 2, got 1)`, `too many values to unpack (expected 2)`, and, with a starred target, `not enough values to unpack (expected at least 2, got 1)`. A list the checker can see is empty — the literal `[]` — has a length known before the run, and is refused then, in the same words, as a type_error. Any other right-hand side is a type_error, where CPython raises `cannot unpack non-iterable int object` as the program runs. That covers a value joined from two types, which CPython unpacks when it holds a collection, and the set of subjects a store read answers with, which a list made from it unpacks. In a comprehension clause a nested group below a list is typed with the values beside it, so a `set`, a `dict`, a view or an iterator there stays a type_error, and a `string` there unpacks: `[a for a, (b,) in [[{1}, {2}]]]` is refused. - **Nested targets** — a target may itself be a parenthesized target list, so `(a, b), c = ` (or `a, (b, c) = `, and deeper) destructures a tuple or list of tuples or lists in one statement. Each nesting level is checked for **exact arity** independently, a tuple's before the run and a list's as it runs: `(a, b), c = ((1, 2, 3), 4)` is a type_error because the inner tuple has three values, not two, and `(a, b), c = [[1], [2]]` refuses `not enough values to unpack (expected 2, got 1)`. A fully-parenthesized top-level list is the same as the unparenthesized one — `(a, b) = t` is `a, b = t` — while a single trailing comma makes a one-element tuple target: `(a,) = t` binds `a` to `t`'s sole element (and `(a) = t`, a bare parenthesized name, is the plain assignment `a = t`). A target list may be written in square brackets as well, at the top level or nested and in each of the three positions below, and means what the parenthesised one means: `[a, b] = t` is `a, b = t`, and `for [k, v] in d.items():` is `for k, v in d.items():`. One target in brackets is a list of one, so `[a] = t` is `(a,) = t` and `[*rest] = t` takes every element of `t`. No target in parentheses or brackets, `()` or `[]`, is a list of none, as in Python, and the value must hold nothing. `[] = xs` binds no name, and when `xs` holds an element it is the catchable refusal `too many values to unpack (expected 0)` as it runs. `for () in xs:` asks the same of each element. Parentheses around a single target only group it, as in Python, so `(xs)[0] = v` writes `xs[0]` and `(x) += 1` is `x += 1`. - **Starred target** — **one** target in the list may be starred (`*rest`); it absorbs the elements the fixed targets do not, as a **`list`** (Python's rule: a starred target is always a list, even from a tuple). `a, *rest = (1, 2, 3)` gives `a = 1`, `rest = [2, 3]`; the star may lead or sit in the middle (`*init, z = t`, `a, *mid, b = t`), and it absorbs zero elements when the tuple is exactly the fixed count (`a, *mid, b = (1, 2)` → `mid = []`). The RHS must be a tuple with **at least** as many elements as the fixed targets (too few is a type_error), or any of the values above, whose length is checked as it runs. Because the absorbed elements become one `list`, they must convert as `list(...)` converts a tuple (§8.13) — a run holding a collection beside a value of another type is a type_error (index the tuple by position instead). A list may hold **at most one** star, and a lone `*rest = t` (no sibling, no trailing comma) is rejected. A sibling or a trailing comma makes it a list. A parenthesized group is a target list of its own, at any depth, so it may hold its own star on the same two terms: `a, (b, *c, d) = 1, (2, 3, 4, 5)` gives `c = [3, 4]`, its width a minimum checked as the group's exact width is, and `(*c)` alone is refused where `(*c,)` takes the whole group as a list. `(a, *b) = t` is `a, *b = t`. A second star and a lone one are refused as Python's compiler refuses them, where the list begins, so `a, *b, *c = t` is a parse_error at column 1 and `for *b in xs:` one at column 5. Each is a decision (§10.1), so a grammar error later in the statement is the answer instead: `*b = 1 +` is a parse_error at column 9. A star alone in parentheses is Python's parser's to refuse, a grammar error where the star stands: `(*b) = t` is a parse_error at column 2, "cannot use starred expression here", as Python names it, where a star alone with no parentheses keeps its own lesson. The star may stand before any target the list takes, as in CPython. Before a group it unpacks the list it absorbs, so the group's members bind what they would bind standing in its place: `a, *(b, c) = 1, 2, 3` gives `b = 2` and `c = 3`, and `a, *[b, *c] = t` is `a, b, *c = t`. Before an element it writes the list there: with `xs = [[0]]`, `a, *xs[0] = 1, 2` makes `xs[0]` the list `[2]`. `*(b)` is `*b`. A comprehension clause checks a starred group's width over what its star absorbs, as a statement does: running `[b for a, *(b, c) in [[1, 2]]]` says `not enough values to unpack (expected 2, got 1)`. An element whose width is in its type, a tuple, is refused at compile in those words. - **Element targets** — a target may be an element or a slice of a collection a name reaches, `xs[i]`, `d[k]`, `m[i][j]` or `xs[i:j]`, and it is written as the element write `xs[i] = v` writes it (§8.14), changing the collection in place and keeping its type. The right-hand side is evaluated first, then the targets are assigned **left to right**, each target's receiver and index read when its turn comes, as in CPython: `i, xs[i] = 1, 5` binds `i` and then writes `xs[1]`. So the swap idiom `xs[i], xs[j] = xs[j], xs[i]` exchanges two elements. An element target may sit in a nested target list, and one alone in parentheses, `(xs[0]) = v`, is the element write. The collection may also be one no name holds, what a call, a literal or a group answers, as in `a, f(xs)[0] = 1, 2`, written as §8.14 writes it. A field is not a target, and a field written as one, `a, s.f = 1, 2`, is refused as §13.3 says. A starred target is any target, as the starred bullet above says. - **`for`-loop unpacking** (`for a, b in :`) takes each **element** of the finite iterand as a `tuple` of the target arity — the shape `enumerate` (§8.6), `zip` (§8.6), and a dict's `.items()` (§8.6) produce, which is what makes this the idiomatic way to consume them — or as a `list` or a variadic tuple, whose length is checked as each element is reached, so the elements before a short one have run the body. Each target binds per iteration as an assignment in the enclosing scope, so, like the single loop variable, it holds the last element's value after the loop (§6.1) and keeps its type as any re-assignment must. A trailing comma ends the list: `for a, b, in xs:` is `for a, b in xs:`, and `for a, in xs:` takes each element as a one-element tuple and binds `a` to its item. A `for` target, alone or in a list, may be an element or a slice: `for xs[0] in ys:` writes `xs[0]` with each element of `ys` before the body runs, as `xs[0] = y` would. Nested and starred targets are taken here too (`for a, (b, c) in xs:`, `for a, *rest in xs:`); with a star the target arity is a **minimum** rather than an exact width. - **Comprehension-clause unpacking** (`[ for a, b in ]`) applies the same rule in expression position, in every clause of a list, set or dict comprehension — so the three projections above are consumed the same way a `for` statement consumes them: `[k for k, v in inventory.items()]`. Each element must be a `tuple` of the target arity, a `list` or a variadic tuple, refused with the same sentence the loop gives; with a starred target the arity is a **minimum**, as it is everywhere else. Nested and starred targets are taken here too (`[c for a, (b, c) in xs]`, `[r for a, *r in xs]`). The targets are **comprehension-local** (§8.9) and a later clause may rebind one, which shadows it from that point. **One target list, and one exception.** A target list of plain names, of **nested** targets, and of one optional **starred** target is available in all three positions above: one target list is defined, and every position that takes one takes the same one. The single exception is an **element** or **slice** target, available in an assignment and a `for` header but not in a comprehension clause, which takes names only. An element there is a parse error that names the `for` statement as the place it may go. A target list without its `=` is a parse error naming the targets as the parser read them, an element shown as `xs[…]`, and a call, such as `xs[0].copy()`, is not a target. A target Python cannot assign to is a parse_error where it begins, as in Python, naming what it is, when the targets before an `=`, or between `for` and `in`, are no target list Python reads: `[f()] = [1]` is a parse_error at column 2, "cannot assign to function call", and `for b + 1 in xs:` one at column 5, "cannot assign to expression". The first such target is answered, after a conditional with no `else` among them unless a `:` follows its condition, and `True`, `False` and `None` are refused as the reserved words they are. In a chain, the targets weighed are those before the first `=` that follows the target lists Python reads, each before its own: `x = f() = 1` is a parse_error at column 5, "cannot assign to function call". A `lambda`'s header is read past to find its `=`, so `a = lambda: 1 = 2` is one at column 5, "cannot assign to lambda". Python reads such a statement first as expressions, and two answers from that reading come before this one. Where an expression and a `,` begin the statement, an `=` after one of the expressions that follow, each read as a named expression is (§10.1), is answered as a named expression's `=` is, where what it would assign to begins: `a, b c = 1` is a parse_error at column 6, and `a, f() = 1, 2` one at column 4, "cannot assign to function call here". Where no rule of Python's answers, the statement is answered where Python's reading of it as expressions stops: `a, -b c = 1 = 2` is a parse_error at column 7. A target list that stops where those expressions do keeps the answer that names what it read. A `for`'s targets Python does not read, where it names none of them, are answered where its first reading of them stops: past each target it reads, and past what begins the first it does not, with any call, subscript or attribute after it. `for b() < c in xs:` is a parse_error at column 9. Where that reading stops at the `in`, as in `for (b() < c) in xs:`, the answer there, at column 15, is a loop variable missing before it. A bracket among the targets whose inside does not parse is read into as Python reads it, and answered where that reading stops: `a, (b +), c = 1` is a parse_error at column 8, "expected an expression", and a conditional with no `else` there is answered where it begins. A `True`, `False` or `None` Python names past a target the reader stops at is refused where it stands, as in `for (b() < c, None) in xs:`, at column 15. A fixed tuple's arity is in its type, so its check is static; a list's length is a property of the value, so its check runs with the statement, and so does a variadic `tuple`'s (§8.13), whose length is not in its type either. Unpacking introduces no iteration, so termination (§9.5) is preserved. ### 8.12 Chained assignment `a = b = ` binds **every** target on the left to the one value of ``, which is evaluated once: ``` a = b = 0 # a and b both 0 lo = hi = mid = start # three names, one value a = xs[0] = 5 # a name and an element t = a, b = 1, 2 # a name and a target list ``` Each target is any target an assignment takes (§8.11), a name, an element or a slice, or a target list in parentheses, in brackets or in neither, and is written as that assignment writes it, so `t = a, b = 1, 2` binds `t` to the tuple and `a` and `b` to its elements. A field is not a target here either, and is refused as §13.3 says. The targets are assigned **left to right** after the value is evaluated, as in CPython, so `xs[a] = a = 1` writes `xs` at the `a` held before the statement. The value's type is bound to every target, so a later `b = "text"` — reassigning a name first bound as an `int` — is a type_error like any other (the one-type-per-variable rule, §6.1). This is a convenience form for initialising several names to the same value; it introduces no iteration, so termination (§9.5) is preserved. ### 8.13 Type conversions The built-in **scalar conversions** turn a value from one type into another: | Call | Accepts | Produces | |------|---------|----------| | `str(x)` | any expressible value | its text form (`string`) — total; the same rendering `+`/`print` use | | `int(x)` | `string` / `int` / `real` / `bool`, or a `value` holding a number (§5.6) | an `int` | | `int(s, base)` | a `string` + an `int` `base` | an `int` | | `float(x)` | `string` / `int` / `real` / `bool`, or a `value` holding a number | a `real` | | `complex(x)` | `string` / `int` / `real` / `bool` / `complex`, or a `value` holding a number | a `complex` | | `complex(re, im)` | two numbers, each an `int` / `real` / `bool` / `complex` | a `complex` | | `bool(x)` | any value — every value has a truth value (§8.2) | a `bool` — the value's **truthiness** | `int(x)` and `float(x)` do not take a `complex`, as in Python: read `.real` or `.imag` (§5.2), each already a `real`. - `str(x)` never fails — it renders any value the way `print`/`+` do (a number as its decimal, a `bool` as `True`/`False`, a `datetime` as its RFC 3339 text, a collection as its element preview, and a class instance as its class and subject, as §13.2 describes). Note that a `bool` renders `True`/`False`, capitalised and Python-faithful, whether written or read back from the store. Exercised in §C.29. #### 8.13.1 How a store-derived value renders `str(x)` is total, so the types a read gives you back render too, and `print` accepts them (§8.7). They do not share one convention, because they are not one kind of thing: some are collections you might have asked for a million of, one is a report written to be read, and one is a single value wearing a result type. There are **six**, and this is the whole list **for the types a read gives back** — a handler binding is covered at the end of this section. | Value | Renders as | Example | |---|---|---| | `claim_list`, `subject_set`, `string_list`, `result_type_set` | a **counted preview** | `2 entries [e1, e2]` | | `proof_tree` | its **type name and size** | `proof_tree(2)` | | `diagnosis` | **the explanation itself** | `via rule dev_online:` … | | `active_claim` | **the value alone**, by its class | `eng` | | `verify_result` | its **type name and verdict** | `verify_result match=True` | | a read that found **nothing** | the **absence marker** | `` | The **counted preview** is ` entry [` for one and ` entries [` otherwise, then the elements comma-separated, then `]`. An empty collection renders `0 entries []`. It is **bounded**: at most **8** elements appear, and a longer collection ends with `, + more` naming how many were left out — so printing a large read stays a fixed size. Each element renders by its own class, the same way it would if you bound it and printed it: the preview never disagrees with iteration. A `verify_result` appends ` conflicted` when the cell holds competing values — `verify_result match=True conflicted` — so a verdict from a contested location is never mistaken for a settled one. A `diagnosis` renders the report it carries, which **may span several lines**. This is the one of the six that is not a summary: printing it is how the Python Reference teaches `why_not` and `what_needs`, and what it shows is the whole explanation. **`` is what a read that found nothing renders**, and it belongs to the reads that answer with *a thing* rather than with *a collection of* things: `current(…)` on a cell holding no value in force, and `why(…)` where nothing was derived. A collection read does not use it — an empty `get` or `subjects` is `0 entries []`, which is the counted preview doing its job at zero. The two spellings are worth telling apart: `0 entries []` says *this collection is empty*, and `` says *there was nothing to answer with*. **These renderings are for reading, not for parsing.** They are stable enough to put in a transcript and are not a data format: to take a value out of a result, iterate the collection or read the field. Nothing here is promised to a program that scans the text. **A handler binding is not a read result** and is not one of the six. `str(e)` on a caught `refuse` (§10.3) renders the kind, a colon and the message — `refuse: cell holds 2 competing values — …`. Read `e.reason` when you want the message on its own, and `e.discriminator` when you want to branch on what happened; both are stated in §10.3 and are what a program should use. **The words `entry`, `entries` and `more` belong to the rendering**, as `True` and `False` do, and the type names in the table are the language's own identifiers. - `int(x)` from a **`real`** truncates toward zero (`int(3.9)` is `3`, `int(-3.9)` is `-3`); from a **`bool`** yields `1` / `0`; from a **`string`** parses a decimal integer exactly as `int(s, 10)` below does: surrounding whitespace, a leading sign and `_` digit separators placed as in an integer literal (§3.5.2) are allowed (`int(" -7 ")` is `-7`, `int("1_000")` is `1000`). A string that is not a decimal integer (`int("3.5")`, `int("abc")`), or a value out of the 64-bit range, is a **catchable refuse** (§10.3) — Python's `ValueError`. A `real` that is an infinity refuses with `arithmetic_overflow` and one that is a NaN with `value_error`, in Python's words, `cannot convert float infinity to integer` and `cannot convert float NaN to integer`; so do `round(x)`, `math.floor`, `math.ceil` and `math.trunc` of one. - `int(s, base)` parses the `string` `s` in an explicit radix. `base` is `2`..`36`, or `0` to auto-detect the radix from a `0x` / `0o` / `0b` prefix. A prefix is also accepted when it matches `base`; an optional leading sign and `_` digit separators (between digits, or once right after a prefix) are allowed. A digit outside the base, an out-of-range `base`, a malformed separator, 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. - `float(x)` from an `int` / `bool` widens to a `real`; from a **`string`** parses a decimal real, including exponent form (`float("1e3")` is `1000.0`), tolerating surrounding whitespace and taking `_` digit separators placed as in a real literal (§3.5.7) (`float("1_000.5")` is `1000.5`). `inf`, `infinity` and `nan`, in any case and with an optional sign, read as Python reads them (`float(" -Infinity ")` is `-inf`), and a value past the largest `real` is the infinity, as in Python (`float("1e400")` is `inf`). A hexadecimal float (`float("0x1p3")`), any other non-numeric string, and a NaN with a payload (`float("nan(123)")`) are a **catchable refuse** (§10.3). - `complex(x)` from an `int` / `real` / `bool` is that number with a zero imaginary part (`complex(3)` prints `(3+0j)`); from a `complex` it is the value itself; and with no argument it is `0j`. From a **`string`** it reads the text `print` shows for a complex: a real part, an imaginary part ending in `j` or `J`, or both with the imaginary part's sign between them and no space inside (`complex("3+4j")`, `complex("-2.5j")`, `complex("j")`). Surrounding whitespace and one enclosing pair of parentheses are allowed, and each part takes what `float` takes. Any other string is a **catchable refuse** (§10.3) in the words of Python's `ValueError`: `complex() arg is a malformed string`, or, where a `_` is not between two digits, `could not convert string to complex:` and the string. A part spelled `inf` or `nan`, or past the range, is what `float` reads, as in Python: `complex("inf")` is `(inf+0j)`, `complex("nanj")` is `nanj` and `complex("1e400")` is `(inf+0j)`. - A **`string`** that `int(s)`, `int(s, base)`, `float(s)` or `complex(s)` reads is read as Python reads it past ASCII: a character `s.isspace()` calls whitespace pads the number as a space does, and a decimal digit of any script stands for its value, so `int("٥5")` is `55`, `float("١.٥")` is `1.5`, and `int("\u00a05")` is `5`. A digit that is not decimal, such as `²`, is refused as any other character is, and a space inside the number is not padding. ASCII is read as it is, so the four ASCII separators `\x1c`..`\x1f`, which `s.isspace()` also names, are not stripped, as in Python. A refusal quotes the string as it was written. - `complex(re, im)` builds a complex from two numbers, computed as Python computes it. When both are `int`, `real` or `bool`, `re` is the real part and `im` the imaginary part, each keeping the sign of a zero: `complex(1, 2)` prints `(1+2j)` and `complex(1, -0.0)` prints `(1-0j)`. Either may be a `complex`: the real part is then `re`'s real part less `im`'s imaginary part, and the imaginary part is `im`'s real part plus `re`'s imaginary part, so `complex(1j, 2j)` prints `(-2+1j)`. A `string` in either position of the two-argument form is a type_error. - `bool(x)` returns the **truthiness** (§8.2) of any value as a `bool` — `bool([])` is `False`, `bool("x")` is `True`. Every value has a truth value, so it is **total**, like `str(x)`. Each scalar conversion takes none or one argument, `int` also the two-argument `int(s, base)` form above, and `complex` none, one or two. With none each is the zero of its type, as in Python: `int()` is `0`, `float()` is `0.0`, `str()` is `""`, `bool()` is `False` and `complex()` is `0j`. Any other count, or a non-convertible argument type, is a type_error. **Arguments by name.** A built-in function takes by name exactly the arguments CPython lets a caller name, and places each in the position it names, so a named call is the positional call it spells: `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)`, with the names in any order after the positional arguments. Each argument is evaluated where it is written, the positional ones first, whichever position it fills, so `pow(exp=xs.pop(), base=xs.pop())` pops the exponent first, as in Python (§9.1). `zip` takes one keyword after its inputs, `strict`, which has no position (§8.6). The names given to `dict` are not parameters but keys, each the string written, whatever the language: `dict(a=1, b=2)` is `{"a": 1, "b": 2}`. After a positional argument they are written over the entries it holds, in order, so `dict({"a": 1}, a=2, b=3)` is `{"a": 2, "b": 3}`, and the result has the type a literal holding the same entries has. An omitted `real` is `0`, as in Python: `complex(imag=2)` is `2j`. `int`'s text cannot be named, as in Python, and the keyword arguments of `sum`, `min`, `max`, `sorted` and `print` are described with those functions, and a method's in §8.6. Every other built-in function takes no argument by name. A name the function does not take, a name given twice, an argument given both by position and by name, and a required argument left empty are each a compile-time error in CPython's words, such as `abs() takes no keyword arguments` and `round() missing required argument 'number' (pos 1)`. Arguments are counted before any name is read, as CPython counts them, so a call with more arguments in all than the function takes is refused for its length whichever of them are named. After the count, a required argument left empty is found first, then an argument given both by position and by name, then a name the function does not take, in CPython's order. A built-in function given a count it does not take is a type_error at the function's name, in the words of the `TypeError` CPython raises, which follow how the function is written in C and not what it does: `len() takes exactly one argument (2 given)`, `isinstance expected 2 arguments, got 1`, `range expected at most 3 arguments, got 4`, `list expected at most 1 argument, got 2`, `round() takes at most 2 arguments (3 given)`, and `round() takes at most 2 keyword arguments (3 given)` where every argument is named. `len`, `isinstance` and `issubclass` refuse any name before they count, as CPython's do, and `sorted`, `min` and `max` count what they take by position before their names: `sorted(key=abs, reverse=True, x=1)` is `sorted expected 1 argument, got 0`. `enumerate` reads its names where they stand, as CPython's does, and not in that order: with two arguments in all one name must be `start`, and with one it must be `iterable`, so `enumerate(start=1)` is `'start' is an invalid keyword argument for enumerate()`. With more and nothing by position it lacks its iterable, `enumerate() missing required argument 'iterable'`. The count is checked where the unit is compiled, where Python raises as the call runs, so no handler around the call sees it. Two counts are taught what this language provides instead, since CPython's words name what it does not: `iter` given a second argument, whose form calls a function until it answers a sentinel, and `str` given a second, which is an encoding, where no value is bytes. The **collection conversions** build one collection from another: | Call | Accepts | Produces | |------|---------|----------| | `list(x)` | any finite collection, a `string`, or a `tuple` of one type, of scalars or of class instances | a `list` — the elements (a `string` yields its characters) | | `set(x)` | any finite collection of hashable elements, a `string`, or a `tuple` of one type or of scalars | a `set` — the unique elements, sorted | | `dict(x)` | a `dict`, or a finite collection of pairs: a `list>`, a `tuple` of them, a `string` or an items view `dict_items` (§5.1) | a `dict` — the mapping | | `tuple(x)` | any finite collection, a `string`, or a `tuple` of one type, of scalars or of class instances | a **variadic** `tuple` — the elements as an immutable, runtime-length tuple | Called with no argument, each builds the empty collection, as in Python, typed as its literal is: `list()` is `[]`, `dict()` is `{}`, `tuple()` is `()`, and `set()` is the empty set, which has no literal (§3.5.13). A second argument is a type_error. - `list(x)` collects the elements of a finite collection (`list`, `set`, `dict` — over its **keys** —, a dict view (§5.1), or a store-derived collection) into a new `list`; a `string` yields its characters as one-character strings (`list("ab")` is `["a", "b"]`). A **`tuple`** is also accepted: `list((1, 2, 3))` is `[1, 2, 3]`. A `list` has one element type, which is what a `for` over the tuple binds (§5.1): the positions' one type, `value` over scalars of several (§5.6), or the nearest common base of class instances. So `list((1, "a"))` is a `list`. A tuple holding a collection beside a value of another type is a type_error (index it by position instead). - `set(x)` collects the elements into a `set` — **deduped and sorted** (§5.1), an `int`, a `real` and a `bool` of equal value being one element (§3.5.13); the elements must be hashable (a collection of unhashable elements is a type_error). The empty set is `set()`, as in Python, or `set([])`. - `dict(x)` builds a mapping from another **dict** — a new dict holding its entries in its order, as `x.copy()` does (§8.14) — or from any other **finite collection of pairs**, read as `update` reads one (§8.14): a pair is any collection of two, a key and a value, so a list, a tuple, an iterator or an items view of two-element tuples is the shape `zip` (§8.6) and a dict's `.items()` (§5.1) produce, and `dict(zip(keys, vals))` and `dict(pairs)` are the idiomatic constructions. A duplicate key keeps its first position and takes the last value (§8.9). An element that is no collection, or one not two long, is refused when it is read, catchably, in Python's words (§10.3). What cannot be iterated at all is a type_error. - `tuple(x)` collects the elements into a **variadic homogeneous tuple** `tuple` — a tuple of **runtime** length whose every element is `T`. Unlike a fixed `tuple` (from a tuple literal), it is indexed by a **runtime** `int` (`t[i]` → `T`, out-of-range refuses) and **unpacks as a list does**, its length checked when the statement runs (§8.11). It compares (`==`) against another tuple whose elements agree with `T`. A fixed **`tuple`** is accepted too, which is how a fixed tuple is widened to a variadic one: `tuple((1, 2))` is a `tuple`, its `T` what a `for` over it binds (§5.1). One holding a collection beside a value of another type is a type_error — there is no single `T` to collect — so index it by position instead. A tuple does not change, so `tuple(xs)` holds the elements `xs` held when it was called, whatever changes `xs` after. **`repr(x)`** returns the **repr** text of any value as a `string` — a `string` keeps its quotes (`repr("hi")` is `"'hi'"`), a collection renders with repr elements; for most other values `repr` matches `str`. Past U+007F a string's repr writes a character as itself when Python's `str.isprintable()` accepts it, and escapes any other by its size as `ascii(x)` does. Those are the controls, the format characters, the private-use and unassigned codepoints, and every separator but the space: `repr("a\xa0b")` is `"'a\\xa0b'"`, and `len(repr("a\xa0b"))` is `8`, as in Python. **`ascii(x)`** returns `repr(x)` with every character past U+007F escaped by its size — two hexadecimal digits up to U+00FF, four up to U+FFFF, eight beyond — as Python escapes it: `ascii("é")` is `"'\\xe9'"` and `ascii(["日"])` is `"['\\u65e5']"`. Where there is nothing to escape it equals `repr(x)`. **`bin(n)`**, **`hex(n)`** and **`oct(n)`** write an `int` in base 2, 16 or 8 behind the prefix `0b`, `0x` or `0o`, in lowercase digits and at no width: `bin(5)` is `"0b101"`, `hex(255)` is `"0xff"`, `oct(0)` is `"0o0"`. A negative number is signed, never written in two's complement — `bin(-5)` is `"-0b101"` — and a `bool` is the int it is, so `bin(True)` is `"0b1"`. Each reads back through `int(s, base)`, which takes the prefix (`int(hex(255), 16)` is `255`). An argument of any other type is a type_error, and so is a count other than one, for all four. **`format(value, spec)`** renders a value per the Python **format mini-language**, returning a `string`; it also powers an f-string `{value:spec}` (§3.5.10). `format(value)` takes the empty spec. The supported spec grammar is `[[fill]align][sign][z][#][0][width][grouping][.precision][type]`: - **align** — `<` left, `>` right, `^` center, `=` between a number's sign and its digits; an optional **fill** character precedes it (default space), and it may be any one character, `é` or `中` as well as `*`. With no align, numbers are right-aligned and other values left-aligned. `format(-5, "*=6")` is `"-****5"`. A `string` refuses `=` in Python's words, `'=' alignment not allowed in string format specifier`, and a `complex` in Python's words too, `'=' alignment flag is not allowed in complex format specifier`; a collection refuses it in ours. - **sign** — `+` writes a sign on a positive number too, a space writes a space there, and `-`, as with no sign, writes only a negative's: `format(5, "+")` is `"+5"` and `format(5, " ")` is `" 5"`. A `string` refuses each in Python's words, `Sign not allowed in string format specifier` and `Space not allowed in string format specifier`, and `c` refuses one too, `Sign not allowed with integer format specifier 'c'`. - **`z`** — a `real` or a `complex` that writes as a negative zero writes no sign: `format(-0.001, "z.1f")` is `"0.0"`. An `int` and a `string` refuse it in Python's words, `Negative zero coercion (z) not allowed in integer format specifier`. - **`#`** — `b`, `o`, `x` and `X` write their prefix, `format(5, "#b")` being `"0b101"`, and a `real` keeps its point and a `g`'s trailing zeros: `format(1.0, "#.0f")` is `"1."` and `format(1.0, "#g")` is `"1.00000"`. A `string` refuses it, `Alternate form (#) not allowed in string format specifier`, and so does `c`, `Alternate form (#) not allowed with integer format specifier 'c'`. - **`0`** — the fill becomes `0`, and a number written with no align is padded after its sign: `format(-5, "05")` is `"-0005"`. Any other value with no align is left-aligned as before, so `format("ab", "05")` is `"ab000"`. After a written fill the `0` is the first digit of the width, so `format(5, "*>05")` is `"****5"`. An infinity or a NaN is padded with spaces instead, as C's `printf` pads one: `format(math.inf, "010")` is `" inf"` and `format(-math.inf, "010")` is `" -inf"`, where Python writes `"0000000inf"` and `"-000000inf"`. A written fill of `0` is still the fill: `format(math.inf, "0>10")` is `"0000000inf"`, as in Python. - **width** — the minimum field width in characters. - **grouping** — `,` separates a number's digits in threes, and `_` too, or in fours under `b`, `o`, `x` and `X`: `format(1234567, ",")` is `"1,234,567"` and `format(255, "_b")` is `"1111_1111"`. A fill of `0` is grouped with them and may pass the width by one: `format(1234, "08,")` is `"0,001,234"`. A type that takes no grouping refuses it in Python's words, `Cannot specify ',' with 'x'.`, and so does writing both, `Cannot specify both ',' and '_'.`. - **`.precision`** — for a `real`, the digits it is written with: after the point under `f`, `F`, `e`, `E` and `%`, and **significant** digits under `g`, `G`, `n` and with no type. For a `string`, the maximum character count. A precision under an integer type is a catchable refuse in Python's words, `Precision not allowed in integer format specifier`. - **type** — for an `int` or a `bool`: `d` and `n` (decimal), `x` / `X` (lower / upper hex), `o` (octal), `b` (binary), `c` (the character whose code point it is, as `chr` gives it), and each type a `real` takes, which writes the number as a `real`. For a `real`: `f` / `F` (fixed point), `e` / `E` (scientific, the exponent signed and at least two digits), `g` / `G` (fixed point, or scientific when the exponent is below −4 or at least the precision, with trailing zeros removed), `n` (as `g`) and `%` (the value times 100 in fixed point, then `%`); each takes a precision of 6 when none is written. For a `string`: `s`. With no type a value renders as `str`, and a precision with no type on a `real` counts significant digits as `g` does, but keeps the `.0` of a whole number and turns scientific one digit sooner, as Python does: `format(99.0, ".3")` is `"99.0"` and `format(100.0, ".3")` is `"1e+02"`. The types above are the whole set Python's `int`, `float` and `str` take. So `format(3.14159, ".2f")` is `"3.14"`, `format(42, ">5")` is `" 42"`, `format(42, "05")` is `"00042"`, `format(255, "x")` is `"ff"`, `format(1234.5, ".2")` is `"1.2e+03"`, `format(255, "e")` is `"2.550000e+02"` and `format(65, "c")` is `"A"`. The digits are the exact value of the `real` rounded to the nearest, a tie to the even digit: `format(2.5, ".0f")` is `"2"` and `format(0.125, ".2f")` is `"0.12"`. A bool formats as the `int` it is once any spec is written: `format(True, ">3")` is `" 1"`. A type the value's own type does not take is a catchable refuse in Python's words, `Unknown format code 'q' for object of type 'int'`. So is a width or precision too long to read, `Too many decimal digits in format string`; a precision above 2147483647 where a number is written as a `real` or a `complex`, `precision too big`; and `c` of a number outside `0` … `0x10FFFF`, `%c arg not in range(0x110000)`. `c` of a lone surrogate, `0xD800` … `0xDFFF`, is refused as `chr` refuses it, where Python answers, because a string here is UTF-8 and has no encoding for one. A collection (a list, tuple, dict, set, a dict's views, a slice) takes no format spec: a non-empty one is refused, a catchable refuse in our own words, 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 it does in Python. It is right-aligned by default, like a number. With `f` each part takes the precision and the parentheses are dropped: `format(3 + 4j, ".2f")` is `"3.00+4.00j"`. A precision with no type counts significant digits in each part and keeps the rendered shape: `format(3.14159 + 2j, ".3")` is `"(3.14+2j)"`. Zero padding, the `0` flag or a `0` fill, is a catchable refuse in Python's words, `Zero padding is not allowed in complex format specifier`. A `complex` takes `e`, `E`, `f`, `F`, `g`, `G` and `n`, each part written as a `real` is under the same type, the imaginary one with its sign; with `n` or no type the parts take `g`. Any other type is refused in Python's words, as for any value above. ### 8.14 Changing a collection A list, a set and a dict are **shared**. A name, a parameter, an element of another collection and a loop variable that hold one hold the same collection: `b = a` binds `b` to the list `a` holds rather than to a copy of it, and a script called with a list receives that list. A statement that changes a collection changes it **in place**, so every holder sees the change, as in Python. Each of these statements answers nothing, and each is a statement of its own (`rebind-stmt`, `del-stmt`, §4.3), never an expression. The three methods that change a collection and answer a value follow the table. | Statement | Receiver | Changes the collection to | |---|---|---| | `xs.append(v)` | list | `xs + [v]` | | `xs.extend(c)` | list | `xs` followed by the elements of `c`: a list, tuple, set, string, or a dict's keys | | `xs.insert(i, v)` | list | `xs` with `v` placed before position `i`; a negative `i` counts from the end, and the position is clamped to `0` … `len(xs)` | | `xs.remove(v)` | list | `xs` without its first element equal to `v` | | `xs.sort(key=f, reverse=b)` | list | `sorted(xs, key=f, reverse=b)` (§8.13); both keywords are optional, and a positional argument raises `sort() takes no positional arguments`, as in Python | | `xs.reverse()` | list | `xs` in reverse order | | `c.clear()` | list, set, dict | the empty collection of its kind | | `s.add(v)` | set | `s \| {v}` | | `s.discard(v)` | set | `s - {v}` | | `s.remove(v)` | set | `s - {v}`, refusing when `v` is absent | | `s.update(c, …)` | set | `s` with the elements of each `c`; with no `c`, `s` | | `s.difference_update(c, …)` | set | `s` without the elements of any `c`; with no `c`, `s` | | `s.intersection_update(c, …)` | set | `s` with only what every `c` holds; with no `c`, `s` | | `s.symmetric_difference_update(c)` | set | `s ^ set(c)` | | `d.update(e)` | dict | `d` with each entry of the dict `e`, or each pair `e` holds, written in order. A pair is any collection of two, a key and then a value, so `d.update(["ab"])` writes `d["a"] = "b"`. A pair of another length is `dictionary update sequence element #0 has length 3; 2 is required`, an element that is no collection `cannot convert dictionary update sequence element #0 to a sequence` and an `e` that is no collection `'int' object is not iterable`, each a catchable refusal (§10.3) in Python's words | | `d.update(k=v, …)`, `d.update(e, k=v, …)` | dict | `d` updated by `e` when it is given, then with each name written as a string key mapped to its value, in the order written: `d.update(a=1)` writes `d["a"] = 1`, whatever the language, as `dict(a=1)` builds `{"a": 1}` (§8.13). `d.update()` changes nothing | | `xs[i] = v` | list | `xs` with position `i` holding `v` | | `d[k] = v` | dict | `d` with `k` mapped to `v`; a key `d` already holds keeps its position | | `xs[i] op= v`, `d[k] op= v` | list, dict | the element write of `xs[i] op v` or `d[k] op v`, with `i` or `k` evaluated once; an element that is itself a list or a set is changed in place, as `op=` changes one held by a name | | `del xs[i]` | list | `xs` without position `i` | | `del d[k]` | dict | `d` without the key `k` | | `xs[i:j] = c` | list | `xs` with the elements from position `i` up to `j` replaced by the elements of `c`, which may be more or fewer; the bounds are read as a slice's are (§5.3) | | `xs[i:j:k] = c` | list | `xs` with each position the slice selects holding the next element of `c`, which must hold exactly as many; a step of `1` writes as the plain slice above does | | `xs[i:j] op= c` | list | the slice write of `xs[i:j] op c`, with the bounds evaluated once | | `del xs[i:j]`, `del xs[i:j:k]` | list | `xs` without the positions the slice selects | | `xs[s] = c`, `xs[s] op= c`, `del xs[s]` | list | with `s` a `slice` value (§5.3), what the same statement does with the bounds `s` holds written in the brackets: `xs[slice(1, None, 2)] = c` is `xs[1::2] = c` | | `xs += c` | list | what `xs.extend(c)` changes it to, for any finite collection `c` (§4.3) | | `xs *= n` | list | `xs * n` (§4.3) | | `s \|= t`, `s &= t`, `s -= t`, `s ^= t` | set | `s \| t`, `s & t`, `s - t`, `s ^ t` (§4.3) | So `a = [1]`, `b = a`, `a.append(2)` leaves `b` as `[1, 2]`; a script that appends to its parameter appends to the caller's list; and `row = [0]`, `grid = [row, row]`, `row.append(1)` leaves `grid` as `[[0, 1], [0, 1]]`. An element read answers the element itself, so `r = grid[0]`, `r.append(2)` changes the row `grid` holds. **Changing and answering.** Three methods change a collection and answer a value. Each is an expression as well as a statement, and stands wherever an expression may: in an operator, an argument list, a comprehension, a condition, an f-string, or on an element. | Call | Receiver | Answers | Changes the collection to | |---|---|---|---| | `xs.pop()` | list | its last element | `xs` without it | | `xs.pop(i)` | list | the element at position `i`; a negative `i` counts from the end | `xs` without position `i` | | `s.pop()` | set | its first element in the set's iteration order | `s` without it | | `d.pop(k)` | dict | the value `k` maps to | `d` without the key `k` | | `d.pop(k, default)` | dict | the value `k` maps to, or `default` when `d` has no `k` | `d` without the key `k` | | `d.setdefault(k, default)` | dict | the value `k` maps to, or `default` when `d` has no `k` | `d` with `k` mapped to `default` when it had no `k`, and unchanged otherwise | | `d.popitem()` | dict | its last entry, as a `(key, value)` tuple | `d` without that key | The answer is the element itself, as an element read's is, so `row = grid.pop()` holds the row the list held. As a statement, the call changes the collection and the answer is discarded. So `groups = {}` filled by `groups.setdefault(k, []).append(v)` groups each `v` under its `k`: the default is written and then answered, and `append` changes the list the dict now holds. ``` # grouping.dpy — setdefault and pop change a collection and answer a value. def group(): groups = {} for w in ["ant", "bee", "asp", "cat"]: groups.setdefault(w[0], []).append(w) print(groups.pop("c", [])) # ['cat'] words = groups["a"] print(words.pop(), words) # asp ['ant'] print(groups) # {'a': ['ant'], 'b': ['bee']} ``` Invoked, that prints `['cat']`, then `asp ['ant']`, then `{'a': ['ant'], 'b': ['bee']}`, as Python does. **Through an element.** A statement changes the collection its target reaches, which may be an element of another collection or what a method answers: `m[i][j] = v`, `d[k].append(v)`, `del m[i][j]`, `d.get(k, []).sort()`. Everything before the last subscript or the method is read once, as an expression is, and what it reaches is the collection changed — the one its holder holds, so the holder sees the change: after `m = [[1, 2]]`, `row = m[0]` and `m[0][0] = 5`, both `m` and `row` show `5`. Only the collection the target reaches changes, so `d.get(k, []).append(v)` on a dict without `k` appends to the default, which nothing holds, as in Python. **Through a value.** An element or slice write may also begin with a value no name holds: what a call, a literal or a group answers, `f(xs)[0] = v`, `m.rows(xs)[i][j] += 1` or `[xs][0][0] = v`. So may a method that changes a collection, and a `del`: `f(xs).append(v)`, `[xs][0].sort()`, `f(xs).pop()`, `del f(xs)[0]` or `del xs.copy()[0]`. The value is read once, as an expression is, and the collection it reaches is changed, so a script that returns the list it was given hands back the caller's list, and `f(xs)[0] = 9` changes `xs`, as in Python. Parentheses around a name or an element are that name or element, so `(xs).append(v)` and `del (xs)[0]` change `xs`. Parentheses around a list of targets are that list, so `del (a, b)` is `del a, b`, as in Python, and refused as that is. A target Python cannot delete at all, one that is no name, attribute, element or slice, is a parse_error where it begins, as in Python, naming what it is, and the first such target is answered: `del a, f()` is a parse_error at column 8, "cannot delete function call", and `del xs[0] + 1`, whose target is the sum, one at column 5. A conditional with no `else` among the targets is answered before any target is weighed. A target that ends in a call whose arguments do not parse is weighed as the value before the call, as Python backs off a trailer that fails: `del x, None(, 1` is a parse_error at column 8, "cannot delete `None`", where `del x, f(, 1`, whose `f` Python can delete, is one at column 10, inside the call. An `=` that begins a call's argument, straight after its `(` or a comma, names no keyword, so the call does not parse there: `del f(=1)` is a parse_error at column 7. A trailer after the element goes on with the target, as in Python: `del xs[0].a` is refused, an attribute being no element, and a grammar error inside a trailer is answered there, so `del xs[0](1 +)` is a parse_error at column 14. A value side by side after the last target is read whole before any target is weighed, as CPython reads its targets as expressions first, so `del e < b d (` is a parse_error at column 13, the bracket never closed, where `del e < b d` is one at column 5, the comparison. A **field** is not changed this way: it belongs to an instance a name holds and is written whole, with `remember` (§13.3), so `s.f[0] = v` and `f().y = v` are refused. **As one target of several.** An element or slice write may also be one target of a target list, a chained assignment or a `for` header (§8.11, §8.12), and is written there as this statement writes it: `xs[i], xs[j] = xs[j], xs[i]` swaps two elements. Its value may be a bare comma-list, as an assignment's may (§3.5.11): `ts[0] = 3, 4` writes the tuple `(3, 4)`. **What builds a new collection.** A list, set or dict literal, a comprehension, `.copy()`, `list(c)`, `set(c)`, `dict(c)`, `tuple(c)`, `sorted(c)`, a slice read, and the operators `+`, `*`, `|`, `&`, `-` and `^` each build a new collection, so a change to it leaves the operands as they were. So a program that means to change a collection and keep the original takes a copy first: `ys = xs.copy()`, `t = s.copy()`, `e = d.copy()` or `e = dict(d)`. The new collection is shallow, as Python's is: its elements are the ones its operands hold, so `grid2 = grid.copy()` holds the same rows as `grid`. An assignment `x = e` binds `x` to what `e` answers — a new collection only when `e` builds one — and changes no collection `x` held before. **Typing.** The changed collection must have the collection's type, as every assignment must (§6.4). `xs.append(v)` is typed as `xs + [v]`, so appending a `string` to a `list` is a type_error naming the type change, exactly as `xs += ["a"]` is. `s.add(v)` is typed as `s | {v}`, and `d[k] = v` as a dict literal holding `d`'s entries and `k: v`. An `int` and a `real` meet as §5.6 says. A set element and a dict key must be hashable (§5.1). A method the receiver's type does not take — `append` on a set, `add` on a list, any of them on a string or a tuple — is a type_error in Python's words, `'set' object has no attribute 'append'`, and an element write or `del` on a string or a tuple is a type_error too, in Python's words, as on any value that holds no element it could change: `'str' object does not support item assignment`. Rebuild it from slices (`s = s[:i] + c + s[i + 1:]`). A method no value takes, called on what an expression answers, is told the same way: `xs[0].foo()` on a `list` says `'int' object has no attribute 'foo'`, on a `list` `'str' object has no attribute 'foo'`. What only this language has names its own type, and a `value` names the receivers that take the method. A change read as a value answers `None` in Python, which no value here holds, so `y = xs.append(1)` is a type_error saying so. Beside a `**` before a dict whose keys only the program knows (§8.15), a change read as a value and bound to no name waits for the program instead: the names are merged and refused as at its statement, the count is checked, the change is made, and only then is the value refused, in the same words. With `opts` empty, `print(xs.append(2, **opts))` appends `2` to `xs` and is then refused; with `opts` holding `x`, it is `list.append() takes no keyword arguments`, and `xs` is as it was. So it is through a type's name, `list.append(xs, 2, **opts)`, and at a list's `sort`, which reads the names `sorted` takes. A value bound from it, `y = xs.append(2, **opts)`, is a type_error where the unit is compiled, whatever the mapping gives (§8.15). A slice write and `del` of a slice are a list's alone, and on anything else are a type_error; a slice write's value is any collection, typed as `extend`'s argument is. A change through an element keeps the element's type as a change through a name keeps the name's: `m[0].append("a")` on a `list>` is a type_error naming `m[…]`. A list's or a set's `pop` answers the element type, a dict's `pop` its value type and `popitem` a `tuple`. A list's `pop` takes at most one argument, an `int`; a set's `pop` and `popitem` take none; a dict's `pop` takes a key and an optional default, and `setdefault` a key and a default. Any other count is a type_error. A key fits the dict's key type as `d[k]`'s does (§8.6). `d.pop(k, default)` answers the type `d.get(k, default)` does. `setdefault` is typed as the write `d[k] = default`, so its default must have the dict's value type. A collection first bound empty (`[]`, `set()`, `{}`) has an element type nothing has supplied yet. Every name that holds it shares that element type, and the first statement that supplies one supplies it to all of them: after `a = []`, `b = a` and `a.append(1)`, `b` is a `list`. A script parameter supplies one too, so `acc = []` passed to a script taking `list` is a `list` from then on, and so does the default of `d.get(k, default)`, `d.pop(k, default)` or `d.setdefault(k, default)` for a dict's value type: `counts = {}` filled by `counts[w] = counts.get(w, 0) + 1` is a `dict`, and `groups = {}` filled by `groups.setdefault(w, []).append(n)` a `dict>`: an empty default is one type with the dict's values, so what is appended to the answer types the dict. An empty collection written into one whose elements are collections of its kind takes their type in turn: `grid.append([])` on a `list>` and `d[k] = {}` on a `dict>` are accepted, and the new element holds `int`s from then on. A collection built from one still empty shares its element type as well — `b = a.copy()`, `b = list(a)`, `b = a[:]`, `b = a + []` — because a loop can build it again after the source holds elements. A second element type through any of them is a type_error naming the name that supplied the first: after the lines above, `b.append("x")` is refused as `b` sharing its element type with `a`. A collection cannot be one of its own elements, at any depth: `a.append(a)` would need an element type that contains itself, which no type spells, so a program that would build one is a type_error. An element read before any statement supplied one takes its type from the operator it meets. `+`, `-`, `/`, `//`, `**`, `|`, `&` and `^` join two values of one type, so the other operand supplies it: `counts[w] += 1` under `if w in counts:` makes `counts` a `dict`, as `counts[w] = counts[w] + 1` does. `x /= n` stores a division's answer where it read, so there the element is a `real`. `*` and `%` supply nothing, since `2` beside `*` fits an `int`, a `string` and a `list` alike and `%` also formats a string. A read nothing supplies a type for (`v = d[k]` with nothing yet written to `d`, or `d[k] * 2`) is a type_error. Its message names the annotation that would supply one: `d: dict = {}`. A key or index nothing wrote still compiles, and `d["a"] += 1` on an empty dict raises `'a'` at runtime, catchably, as Python's does. **Evaluation.** Everything is evaluated before the collection changes, in the order Python evaluates it, except what a set's `update` and `difference_update` and a dict's `update` read one element at a time (below). An element or slice write evaluates its value first, then its target left to right: the chain, then the index, key or bounds. Every other statement evaluates its target first — a method call its arguments after the chain, left to right, and an augmented write its index, key or bounds once, then the read of the target, then its right-hand side. A call that changes a collection and answers a value runs where Python evaluates it within its expression (§9.1), so after `xs = [1, 2]`, `xs.pop() - xs.pop()` is `1`; in the right operand of `and` or `or`, or in a branch of a conditional expression, it runs only when that operand is evaluated. **One element at a time.** A set's `update` and `difference_update`, and a dict's `update`, read an iterator (§8.6) or a generator expression (§8.9) given to them one element at a time, as Python's do, and each element changes the collection before the next is read. A generator expression's first source is evaluated with the call's other arguments, as Python evaluates it where the generator is made, and the rest of it as each element is read. So a read of the collection being changed finds it as far as the call has changed it, and the read after its size has changed is a catchable `refuse` (§10.3) in Python's words, `Set changed size during iteration` or `dictionary changed size during iteration`, discriminator `changed_during_iteration`. What was written before stays written: after `s = {1, 2}`, a caught `s.update(x + 10 for x in s)` leaves `s` as `{1, 2, 11}`, and a caught `s.difference_update(iter(s))` leaves it as `{2}`. An earlier argument's change is one a later argument's first read finds, so `s.update([5], iter(s))` is refused too. In a dict's `update` a pair of the wrong length is refused naming its position in what gave it, after the pairs before it are written, and the names written after it (`d.update(e, k=v)`) are written once it has been read to its end. Every other argument is read whole when the call reaches it, and `intersection_update` and `symmetric_difference_update` read theirs whole before the set changes, as Python's do. **Refusals.** A change the collection cannot make is a catchable `refuse` (§10.3), whose reason is Python's `str(e)`: - `xs.remove(v)` with no element equal to `v`: `list.remove(x): x not in list`, discriminator `value_not_in_list`; - `s.remove(v)` with `v` absent: the repr of `v`, discriminator `key_not_found`; - `del d[k]` with `k` absent: the repr of `k`, discriminator `key_not_found`; - `xs[i] = v` or `del xs[i]` with `i` outside `-len(xs)` … `len(xs) - 1`: `list assignment index out of range`, discriminator `index_out_of_bounds`; - `xs[i:j:k] = c` with a step other than `1`, whose slice selects a different number of positions than `c` holds: `attempt to assign sequence of size N to extended slice of size M`, discriminator `value_error`; - a slice write or `del` of a slice with a step of `0`: `slice step cannot be zero`, discriminator `slice_step_zero`; - `xs.pop()` on an empty list: `pop from empty list`, and `xs.pop(i)` with `i` outside `-len(xs)` … `len(xs) - 1`: `pop index out of range`, both with discriminator `index_out_of_bounds`; - `s.pop()` on an empty set: `'pop from an empty set'`, discriminator `key_not_found`; - `d.pop(k)` with `k` absent and no default: the repr of `k`, discriminator `key_not_found`; - `d.popitem()` on an empty dict: `'popitem(): dictionary is empty'`, discriminator `key_not_found`; - a read in the target refuses as that read does: `m[5][0] = v` with a shorter `m` is `list index out of range`, and `d["z"].append(v)` without `"z"` the repr of `"z"`. **A loop over a collection its body changes.** A `for` reads a list as it stands at each step, as Python does: step `i` binds the element at position `i` when the step begins, and the loop ends when `i` reaches the list's length. It never runs more steps than the list had elements when the loop began (§9.5), so a body that removes elements ends the loop sooner, as in Python, and a body that appends ends it at that count. A `for` over a dict or a set walks the keys or elements it held when the loop began; when the dict or set has a different size at a later step — the step after the last element included — that step is a catchable `refuse` (§10.3) in Python's words, `dictionary changed size during iteration` or `Set changed size during iteration`, discriminator `changed_during_iteration`. Writing a value to a key the dict holds does not change its size. A comprehension walks its source the same way. Either over a dict view (§5.1) reads the dict at each step, taking the element at the position the walk has reached, so a value written during the loop is the one bound, and a change of size is refused as it is for the dict. **Divergences from Python, kept.** - A loop that appends to the list it walks stops at the count the list had when the loop began, where Python walks every element appended and may never end: `xs = [1, 2]`, `for x in xs: xs.append(x)` leaves `xs` as `[1, 2, 1, 2]`. The bound is what termination rests on (§9.5). - A list's `extend` given an iterator or a generator expression over the list itself reads it to the length the list had when the call began, where Python's reads every element it appends and may never end: `xs = [1, 2]`, `xs.extend(x for x in xs)` leaves `xs` as `[1, 2, 1, 2]`, the bound a loop keeps (§9.5). - A dict or set whose keys change while its size does not is walked as it stood when the loop began. Python's walk then follows the layout of its hash table, and may bind a key added during the loop or refuse with `dictionary keys changed during iteration`. A dict view is walked by position in the dict as it stands at each step, so removing a key the walk has passed moves the rest back one place and the walk passes over one of them, where Python's does not. - A collection built from one still empty shares its element type, and so does an empty default with the dict's values (Typing, above), where Python lets the two differ. - An element read before anything wrote one takes its type from the operator beside it (Typing, above), so a later write of another type is refused, where Python holds both: after `t[k] += 1`, `t[k] = 0.5` is refused naming `t`. A read beside `*` or `%`, or beside no operator, is refused naming the annotation, where Python runs it. - `s.pop()` answers the set's first element in its sorted iteration order. Python answers whichever element its hash table holds first, which for strings differs from one run to the next. A program that needs a particular element names it: `x = min(s)`, then `s.remove(x)`. - `d.setdefault(k)` without a default is a type_error, where Python writes `None`: DKE Python has no `None` to write, as `d.get(k)` has none to answer. **Not statements of the language.** - `del x` would unbind a name, and a name stays bound from its first binding to the end of its script (§6.1). Bind it to a new value instead. ``` # rebinding.dpy — a statement changes the collection a name holds, in place. def tally(text: string) -> dict: counts: dict = {} order: list = [] for w in text.split(): if w in counts: counts[w] += 1 else: counts[w] = 1 order.append(w) order.sort(key=len, reverse=True) del counts[order[0]] print(order) # ['not', 'to', 'be', 'or'] return counts def show(): counts = tally("to be or not to be") print(counts) # {'to': 2, 'be': 2, 'or': 1} ``` Invoked with `"to be or not to be"`, that prints `['not', 'to', 'be', 'or']` and returns `{'to': 2, 'be': 2, 'or': 1}`, as Python does. `show()` calls it so. ### 8.15 Spreading a collection A `*` before an element of a list, a tuple or a set display **spreads** a collection into it, as in Python: the collection's elements stand in the `*` element's place, in the order a `for` walks them. ``` 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), a tuple written without brackets ``` **What may be spread.** Any value a `for` walks (§6.3) — a list, a set (in its sorted order), a dict (its keys), a dict view, a string (its characters), a variadic tuple, a store-derived collection, and an iterator (§8.6), which the spread reads to its end, so a second spread of it gives nothing. So may a fixed tuple whose positions differ in type, which a `for` does not walk: its length is part of its type, so each position keeps its own. Any other operand is a type_error that names the spread; Python raises a `TypeError` there as the program runs, "Value after * must be an iterable, not int". The operand is a `bitor-expr` (§4.4), as it is in Python, so a comparison, a boolean operator or a conditional after it is a grammar error (§10.1). **Evaluation.** A display's elements and spreads evaluate left to right, each once (§9.1), and a spread reads its operand's elements when it is reached. A spread of an empty collection adds nothing: `[*[], 1]` is `[1]`. The display builds a new collection, so `ys = [*xs]` is a list of its own, and a change made through `ys` is not seen through `xs` (§8.14). A set display keeps the first of equal elements, as it does when they are written out (§3.5.13): `{*[1.0], 1}` is `{1.0}`. Each spread is one operation at the meter, as `a + [3]` and `list(a)` are: the same copy is priced alike however it is spelled (§8.3). **Typing.** A spread of a fixed tuple, `tuple`, is typed as its elements written out in its place: `[*(1, "a")]` is typed as `[1, "a"]` is, a `list`. Any other spread is of a length known only as the program runs, and contributes its element type once, however many elements it holds. A list display is a `list` and a set display a `set` whose `T` joins what each element and each spread contributes, by the list-literal rule (§3.5.9); in a set, each contribution must be hashable (§3.5.13). A spread of the empty list `[]` contributes no type. A tuple display whose spreads are all of fixed tuples is the fixed tuple of its positions written out: `(0, *(1, "a"))` is a `tuple`. One holding any other spread has no length in its type, so it is a variadic `tuple` whose `T` joins its contributions by the list-literal rule, as a concatenation with a variadic tuple joins (§8.4): `(*xs, 3)` with `xs` a `list` is a `tuple`. Contributions that do not join are a type_error, as they are in a list display. **Where a spread stands.** In a list, a tuple or a set display, a tuple written without brackets included: `x = *a, 3`, `return 0, *a` and `for x in *a, *b:`. And in a subscript's index, which it makes a tuple as a comma does, as in Python: `d[*k]` is `d[(*k,)]`. A starred value is no tuple on its own. Standing alone as an assignment's value, after `return`, or as a `for`'s iterable (`y = *a`, `return *a`, `for x in *a:`), it is a parse_error at the `*`, in Python's words, "can't use starred expression here". Python's compiler raises it once the whole source has been read, so a grammar error anywhere in the source is the answer instead. A trailing comma makes it a tuple: `y = *a,` is `(*a,)`. A starred value alone in parentheses is a grammar error at the star (§10.1). As a comprehension's element, `[*x for x in xs]`, it is a parse_error at the `*`, in Python's words, "iterable unpacking cannot be used in comprehension". A `*` before an argument of `print` spreads its operand's elements across `print`'s arguments, so `print(0, *xs)` writes `0` and then each element of `xs`, joined by `sep` (§8.7). Its operand is an `expression`, as in Python, so a conditional may follow the star: `print(*xs if xs else ys)`. A spread of no collection is a type_error at its `*`, as in a display, in the words Python chooses by the call's shape: as the call's only positional argument, keywords beside it or not, "print() argument after * must be an iterable, not int", and otherwise "Value after * must be an iterable, not int". A script's call takes one too, as the next paragraph says; so does a method's call (§13.4), a call into a built-in namespace, a built-in function's call and a built-in method's call (below), and a `*` before the argument of a verb is refused (§10.1). A `**` in a dict display is an entry of its own (§3.5.12), and in a script's call a keyword argument for each entry (below). **A script's call.** A `*` before a positional argument of a call to a script (§6.8) spreads its operand's elements across the call's positional arguments, each filling the next unfilled parameter in its place, and a variadic parameter (§4.2) collects what is left over: given `def f(a: int, b: int, c: int)`, `f(1, *[2, 3])` and `f(*(1, 2), 3)` both bind `a`, `b` and `c` to 1, 2 and 3. The operand is an `expression` (§8.7), and a spread may follow a keyword argument, as in Python: `f(c=3, *xs)` fills `a` and `b` from `xs`. A fixed tuple's length is part of its type, so a call whose spreads are all of fixed tuples has its arguments matched to its parameters at compile of the unit, as written arguments are (§6.8): `f(*(1, 2))` is a type_error. A spread of any other collection has a length known only as the program runs, so its call's arguments are matched as it runs, after every one of them has been evaluated, the positional ones and the spreads first and the keyword ones after, as Python evaluates them. A call they do not fit is then a catchable refusal (§10.3) with discriminator `type_error`, in the words of the `TypeError` Python raises there, checked in Python's order: `f() got multiple values for argument 'b'` when a spread reaches a parameter a keyword argument also names, `f() takes 3 positional arguments but 4 were given` when there are more arguments than parameters and no variadic, and `f() missing 2 required positional arguments: 'b' and 'c'` when a parameter without a default is left unfilled. The count is still checked as written wherever it decides the call on its own: three written arguments to a script that takes two are a type_error whatever a spread beside them holds. Each element of such a spread must suit every parameter it could fill, which is each one from its position on that no keyword argument names, and so must each argument after it: given `def s(a: int, b: string)`, `s(*["a"], "b")` is a type_error at the `*`. A spread of no collection is a type_error at its `*`, in Python's words by the call's shape, which names the script by its module, as Python does: `__main__.f() argument after * must be an iterable, not int` for a script of a unit that is no module, and `m.f()` for one of module `m`. **A `**` at a script's call.** A `**` before an argument of a call to a script or a method (§13.4) gives each entry of a dict as a keyword argument, as in Python: given `def f(a: int, b: int = 2)`, `f(**{"a": 1})` is `f(a=1)` and `f(1, **{"b": 3})` is `f(1, b=3)`. It stands among the keyword arguments, and several may. A dict's keys are known only as the program runs, so its call's arguments are matched then, as with a spread above, and a call they do not fit is a catchable refusal (§10.3) with discriminator `type_error`, in Python's words and order: `keywords must be strings` where a key is no string and no mapping gives a key twice, `__main__.f() got multiple values for keyword argument 'a'` where two arguments name one parameter, which is where two mappings give the same key that is no string too, `__main__.f() got multiple values for keyword argument '1'`, and before any key that is no string is refused, `f() got an unexpected keyword argument 'z'` where a key names none, `f() got some positional-only arguments passed as keyword arguments: 'a'` where it names one that takes only a position, and `f() missing 1 required keyword-only argument: 'b'` where a parameter after a `*` is left unfilled. Each value must suit every parameter it could fill, which is each one no other argument fills, and a dict display whose keys are all written has each value matched to the parameter its key names. An operand that is no dict is a type_error, in Python's words, `__main__.f() argument after ** must be a mapping, not int`. **A `**` at a builtin's call.** A `**` before a dict display whose every key is a literal gives its keys as keyword arguments to a call of `print`, of a builtin, of a built-in type's method, of `sorted` or a list's `sort`, or of a function of a built-in namespace (§8.16–§8.18), as Python's `f(**{"k": v})` is its `f(k=v)`: `int("10", **{"base": 2})` is `2`, `xs.sort(**{"reverse": True})` sorts `xs` in reverse and `math.isclose(**{"a": 1.0, "b": 1.0})` is `True`. Several may stand among the keyword arguments, and one that gives nothing gives the call nothing: `len(xs, **{})` is `len(xs)`. A key given twice in one display keeps its later value, as in Python; where the value it drops does something when it is read, as a call does, the call is a parse_error, since no name holds what it did. A key that is a number, a bool or `None` is a literal too, and is refused as Python refuses it. Such a call is checked where its unit is compiled, as a type_error in Python's words and in Python's order: first a keyword two arguments give, `print() got multiple values for keyword argument 'sep'`, naming a method with its type (`list.sort()`) and a namespace's function with the module Python defines it in (`math.isclose()`, `_struct.pack()`); then a key that is no string, `keywords must be strings`; then what the callee checks, `'zz' is an invalid keyword argument for print()` or `len() takes no keyword arguments`. Where Python's callee checks something first, it is checked first here: `math.log` refuses any keyword before it reads a key (`log() takes no keyword arguments`), as `slice` does (`slice() takes no keyword arguments`), `min` and `max` want an argument before they read a keyword, and a method Python names without its type through a dict, such as `startswith`, is named so (`startswith() takes no keyword arguments`). A name `sorted` does not take is named for `sort()`, which Python's `sorted` passes it to. Any other operand after a `**` at these calls, a name among them, is not read there yet, and the call is a parse_error that says so, except at `print`, at a builtin that takes no name, at a builtin that takes names, `sorted` among them, at a built-in value's method, a change among them, at a function of a built-in namespace, and at the three methods a type answers itself. **A `**` at `print` whose keys the program reads.** At `print`, a `**` before any operand that gives a dict is read where it is written among the keywords, and its keys are merged as the line is composed, in the order Python gathers a call's keywords: each mapping's keys where its `**` stands, the names written in their places. A name given twice is refused as it is met, `print() got multiple values for keyword argument 'sep'`, whether two mappings give it or one gives a name written beside it, and then, once every mapping is merged, a key that is no string, `keywords must be strings`, so that a name given twice is refused before one however the mappings are ordered. A key that is no string is itself a name given twice where two mappings give it, as a dict's keys are equal (`1`, `1.0` and `True` are one key), and it is named as `str` writes it: with `m` and `n` both holding `1`, `print(**m, **n)` is `print() got multiple values for keyword argument '1'`, and with `n` holding `True` it is `... argument 'True'`, the later mapping's spelling. The same holds at every call below, at a builtin, a built-in value's method and a script's call, and where each `**` is a display of literal keys written out, which is refused where the unit is compiled, as a name written twice is. A display written beside a `**` the program reads is merged with it as the program runs, its key that is no string held for the run: with `n` and `k` both holding `"x"`, `print(**{1: 2}, **n, **k)` is `print() got multiple values for keyword argument 'x'` wherever the display stands among them, and `keywords must be strings` only where no name is given twice; a key the display and a mapping give is a name given twice (`1` written, `1.0` held), named as `str` writes the later. Two displays written out that give one name are refused where the unit is compiled only where no `**` the program reads is merged before the second giving: with `n` holding `"x"` and `k` holding `"x"`, `print(**n, **{"x": 1}, **{"z": 1}, **{"z": 2})` and `print(**n, **k, **{1: 2}, **{1: 3})` are `print() got multiple values for keyword argument 'x'`, as the program runs and catchably, since a mapping read before them can give a name twice first, and Python refuses the first it meets; `print(**{"a": 1, "z": 1}, **p, **{"z": 2})` with `p` holding `"a"` names `'a'`. With `p` giving no repeat the name is `'z'`, the same words, as the program runs. A display and a `name=value` that give one name beside such a `**` are the same. Every mapping written out, and a `**` read only after the repeat, keep the refusal where the unit is compiled. Then the first name `print` does not take, written or given, is refused in Python's words alone, `'zz' is an invalid keyword argument for print()`: beside such a `**`, a name written that `print` does not take waits for the merge, as in Python. Then `sep`, before `end`, must be a string or `None`, in the words §4.3 gives a written one, and a `file` the mapping gives is refused in the words a written one is given. Each of these is a catchable refusal (§10.3) with discriminator `type_error`. An operand that is no dict is a type_error where the unit is compiled, `print() argument after ** must be a mapping, not list`. A display there costs nothing of its own; every other `**` is one operation at the meter, and the line is the operation it is when written out. **A `**` at a builtin that takes no name.** At a builtin that takes no keyword argument, such as `len`, `abs`, `list`, `tuple`, `isinstance` or `divmod`, a `**` before any operand that gives a dict is read as Python reads it: the positional arguments first, in the order written, then each name, merged in the order written as at `print`. A name given twice is refused as it is met, `len() got multiple values for keyword argument 'x'`, and then, once every mapping is merged, a key that is no string, `keywords must be strings`. Then any name at all is refused, `len() takes no keyword arguments`, before the arguments are counted. Each of these is a catchable refusal (§10.3) with discriminator `type_error`. A mapping that gives nothing leaves the call as written: `len(xs, **opts)` with `opts` empty is `len(xs)`. An operand that is no dict is a type_error where the unit is compiled, `len() argument after ** must be a mapping, not list`. A count the builtin does not take is refused only where the mapping gives no name, since Python refuses a name first, and then as the program runs: with `opts` empty, `len(xs, 2, **opts)` is `len() takes exactly one argument (2 given)`, and with `opts` holding `x`, `len() takes no keyword arguments`. Without such a `**` that count is a type_error where the unit is compiled. So is an argument the builtin does not take, of a type or a value it refuses, as Python reads one only once the names are merged: with `opts` holding `x`, `len(5, **opts)` is `len() takes no keyword arguments`, and with `opts` empty `object of type 'int' has no len()`, as the program runs, in the words the call has without such a `**`, where it is a type_error as the unit is compiled; and so is `isinstance` given a second argument that names no class. Each such `**` is one operation at the meter, and the call is the operation it is when written out. **A `**` at a builtin that takes names.** At `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum`, `sorted`, `min` and `max`, a `**` before any operand that gives a dict is read as Python reads it, and the names it gives are the call's, each as if written `name=value`. The positional arguments are read first, then each name, merged as at `print`, with the same two refusals. Then, where a name the callee does not take is among them, Python's order holds: too many arguments in all, `int() takes at most 2 arguments (3 given)`, which says keyword arguments where none was given by position and which `zip` counts among its names alone; then a name the call needs and lacks, `pow() missing required argument 'exp' (pos 2)`, or one given by name and by position, `argument for round() given by name ('number') and position (1)`; then the first such name in the order given, `'x' is an invalid keyword argument for int()`. `enumerate` reads the names where they stand instead, as written (§8.13): `enumerate(**opts)` with `opts` holding `start` alone is `'start' is an invalid keyword argument for enumerate()`. `sorted`, `min` and `max` count the names apart from their positional arguments, which are counted as written whatever the mapping gives, and `sorted` is refused past the merge under `sort`'s name, as Python refuses it: `sort() takes at most 2 keyword arguments (3 given)`, `'x' is an invalid keyword argument for sort()`. A name written beside such a `**` that the callee does not take waits for the merge, as Python merges before it counts or reads a name: with `opts` holding `x`, `sorted(xs, x=1, **opts)` is `sorted() got multiple values for keyword argument 'x'` and `min(xs, x=1, **opts)` is `min() got multiple values for keyword argument 'x'`, with `opts` holding a key that is no string `keywords must be strings` at `sorted`, and with `opts` empty `'x' is an invalid keyword argument for sort()`; `min` and `max` read their names in the order gathered, so a name they do not take that stands before a key that is no string is refused first. Without such a `**` that name is a type_error where the unit is compiled. A `key` a mapping gives is never a function, since a dict holds none, and is refused as Python refuses it once there is an element to call it on, `'int' object is not callable`; over no elements the call is the call without it. A `default` beside several positional arguments is refused once the names are read, `Cannot specify a default for max() with multiple positional arguments`. Otherwise the call is the call written out with those names, and refuses what that call refuses, as the program runs, in the words it is refused with where its unit is compiled. Each of these is a catchable refusal (§10.3) with discriminator `type_error`. A call whose type turns on which names are 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 none of the 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)`, and given three names `sum() takes at most 2 keyword arguments (3 given)`. `dict` takes every name as a key, in a new mapping: `dict(**opts)` holds the names given, and `dict(pairs, **opts)` holds `pairs`' entries and then the names, written over any it already holds. Each such `**` is one operation at the meter. **A `**` at a built-in value's method.** At the method of a built-in value called as a value, and at one reached through its type (`str.split(s, **opts)`), a `**` before any operand that gives a dict is read as Python reads it: the receiver and the positional arguments first, then each name, merged as at `print`, a name given twice naming the method with its type, `str.split() got multiple values for keyword argument 'sep'`. A string method that takes names, `split`, `rsplit`, `splitlines` or `expandtabs`, reads them as Python's does, its receiver not counted, and names itself alone: `split() takes at most 2 arguments (3 given)`, `argument for split() given by name ('sep') and position (1)`, `'x' is an invalid keyword argument for split()`. Any other method takes no name, and refuses any it is given in Python's words: `str.upper() takes no keyword arguments`, after a key that is no string, `keywords must be strings`. A count such a method does not take is refused only where the mapping gives no name, as at a builtin that takes no name: with `opts` empty, `s.upper(1, **opts)` is `str.upper() takes no arguments (1 given)` as the program runs, and `xs.count(1, 2, **opts)` is `list.count() takes exactly one argument (2 given)`. So is an argument such a method does not take, through its type too: 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`, as the program runs. A dict's `get` of a key alone, which has no `None` to answer (§8.6), waits for the names too: with `opts` holding `x` it is `dict.get() takes no keyword arguments`, and with `opts` empty it is refused as the program runs in the words it has without such a `**`. Where Python's method refuses any name before it reads one, it is named alone and its keys are not read, `startswith() takes no keyword arguments`; through its type it is named with it. Through its type, a receiver written of another type is refused once the names are merged, as the program runs, before the method reads a name or counts what follows: `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`. Through its type a call given no positional argument, only such a `**`, has no receiver, as one given a `*` of nothing has none: `str.upper(**opts)` is `keywords must be strings` where `opts` holds a key that is no string, `str.upper() got multiple values for keyword argument 'x'` where two mappings give `x`, and otherwise `unbound method str.upper() needs an argument`, as the program runs, after the names; a value bound from it is a type_error where the unit is compiled, as with a `*` of nothing. A mapping that gives nothing leaves the call as written. A method a `bool` has from `int` is named for `bool` at a call that unpacks, with a `*` or a `**`, as Python names a bound method: `True.bit_length(**opts)` is `bool.bit_length() takes no keyword arguments`, where `True.bit_length(x=1)` names `int.bit_length()`. Each of these is a catchable refusal (§10.3) with discriminator `type_error`, and each such `**` is one operation at the meter. **A `**` at a change.** A statement that changes a collection reads such a `**` too, after its receiver and its other arguments, and so does `pop`, `setdefault` or `popitem` wherever it stands, and any other change read as a value, which is made before the value is refused (§8.14). A list's `sort` takes `key` and `reverse` as `sorted` does, counts them apart, and is named alone, as Python names it: `sort() takes at most 2 keyword arguments (3 given)`, `'x' is an invalid keyword argument for sort()`, while a name given twice names it with its type, `list.sort() got multiple values for keyword argument 'reverse'`, as does a name written beside such a `**` that the mapping gives too: `xs.sort(x=1, **opts)` is `list.sort() got multiple values for keyword argument 'x'`, and a name it does not take waits for the merge, `'x' is an invalid keyword argument for sort()` with `opts` empty. Given a positional argument too, it is refused in the words of what the mapping gives: `sort() takes no positional arguments`, or past two arguments in all `sort() takes at most 2 arguments (3 given)`. A dict's `update` takes each name as a key: `d.update(pairs, **opts)` writes `pairs`' entries and then the names. A name given twice, `dict.update() got multiple values for keyword argument 'b'`, is refused before anything is written, and a key that is no string, `keywords must be strings`, once `pairs` has been written and before any name is, as Python's are. Any other change takes no name, and refuses any it is given as a method does above, before the collection changes: `list.append() takes no keyword arguments`, and at a set's `update`, `update() takes no keyword arguments`. A name written beside such a `**` is merged with what it gives and refused with them, as at `print`: with `opts` holding `x`, `xs.append(5, x=1, **opts)` is `list.append() got multiple values for keyword argument 'x'`. A count the change does not take is refused only where the mapping gives no name, since Python refuses a name first: with `opts` empty, `xs.append(2, 3, **opts)` is `list.append() takes exactly one argument (2 given)`. So is an argument the change does not take, `s.add([1], **opts)` being `set.add() takes no keyword arguments` with `opts` holding `x` and `unhashable type: 'list'` with `opts` empty, and so is a dict's `setdefault` of a key alone, which has no `None` to write (§8.14). So is a change that would give its receiver a second type, since a name keeps one type (§6.4): after `s = {1}`, `s.add("a", **opts)` is `set.add() takes no keyword arguments` with `opts` holding `x`, `keywords must be strings` with a key that is no string, and with `opts` empty the refusal §6.4 gives, `assignment to `s` changes its type from `set` to `set` — a variable keeps one type across the function`, as the program runs and not where the unit compiles. It is so at `list.append`, `list.insert`, `list.extend`, a set's `update` and a dict's `update` and `setdefault`, in a `def` as in a module. The statement refuses either way, so the receiver keeps its type after it; at a dict's `update`, which takes its names as keys and so refuses none, the refusal waits for the merge too, a key that is no string is `keywords must be strings`, and nothing is written. A mapping that gives nothing leaves the change as written. Each of these is a catchable refusal (§10.3) with discriminator `type_error`, and each such `**` is one operation at the meter. An operand that is no dict is a type_error where the unit is compiled, before a name written beside it, as Python gathers the names before it calls: `list.append() argument after ** must be a mapping, not list`. A receiver that has no such method is a type_error where the unit is compiled, whatever the mapping gives, as Python looks a method up before it reads an argument: `'tuple' object has no attribute 'sort'`. Through a type's name the method is found, and a receiver written of another type is refused as at a method above, once the names are merged: over a tuple `t`, `list.sort(t, **opts)` is `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object` before a name `sort` does not take is read. Without such a `**`, a name written and a receiver of another type are each a type_error where the unit is compiled, the receiver's first: through a list's `sort`, over a tuple `t`, `list.sort(t, 1)`, `list.sort(t, x=1)` and `list.sort(t, key=len, reverse=True, x=1)` are each `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object`, before the positional argument is counted or the names are, as Python's descriptor refuses a receiver before its method reads an argument. **A `**` at a built-in namespace's function.** At a function of a built-in namespace (§8.16–§8.18), a `**` before any operand that gives a dict is read as Python reads it: the positional arguments first, then each name, merged as at `print`, a name given twice naming the function with the module Python defines it in, `math.isclose() got multiple values for keyword argument 'rel_tol'` and `_struct.pack() got multiple values for keyword argument 'x'`. `math.isclose` reads its names as Python's does and names itself alone: `isclose() takes at most 4 keyword arguments (5 given)`, `isclose() missing required argument 'b' (pos 2)`, `argument for isclose() given by name ('a') and position (1)`, and then `'x' is an invalid keyword argument for isclose()`. Any other function takes no name, and refuses any it is given, `math.sqrt() takes no keyword arguments`, after a key that is no string, `keywords must be strings`, except `math.log`, which refuses any name before it reads a key, `log() takes no keyword arguments`. A mapping that gives nothing leaves the call as written. 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`, with a key that is no string `keywords must be strings`, and with `opts` empty `dict.fromkeys(xs, 0)`. A count a function that takes no name does not take is refused only where the mapping gives no name, as the program runs, as at a builtin that takes no name: with `opts` empty, `math.sqrt(x, 2, **opts)` is `math.sqrt() takes exactly one argument (2 given)`, and without such a `**` that count is a type_error where the unit is compiled. So is an argument such a function does not take: with `opts` holding `x`, `math.sqrt("a", **opts)` is `math.sqrt() takes no keyword arguments`, and with `opts` empty it is refused as the program runs in the words it has without such a `**`, `math.sqrt() takes a real number, not string`. Each of these is a catchable refusal (§10.3) with discriminator `type_error`, an operand that is no dict is a type_error where the unit is compiled, `math.sqrt() argument after ** must be a mapping, not list`, and each such `**` is one operation at the meter. **A `*` beside such a `**`.** Beside a `*` whose length is known only as the program runs, such a `**` is read where what is called takes no name: a builtin that takes no name, a function of a built-in namespace other than `math.isclose`, and a built-in value's method or a change that takes none. Python's order holds. Every argument is read, left to right; then the names are merged, a key that is no string refused, `keywords must be strings`, and a name given twice, `str.upper() got multiple values for keyword argument 'x'`; then the receiver, where the `*` gives it through its type, so that `str.upper(*xs, **opts)` is `unbound method str.upper() needs an argument` while `xs` is empty and `descriptor 'upper' for 'str' objects doesn't apply to a 'int' object` where it gives an `int`; then each name, any of which is refused as above; and then the count the `*` gives, as at a call with a `*` alone. So it is where nothing types what the `*` gives, as with `xs = []`; without such a `**` that call is a type_error where the unit is compiled, in the same words. With `opts` holding `x`, `len(*xs, **opts)` is `len() takes no keyword arguments` whatever `xs` holds, and with `opts` empty, over two elements, `len() takes exactly one argument (2 given)`. So is an argument the `*` gives that the call does not take: over `ys = [1.5]`, `s.center(*ys, **opts)` is `str.center() takes no keyword arguments` with `opts` holding `x`, and `'float' object cannot be interpreted as an integer` with `opts` empty. Each of these is a catchable refusal (§10.3) with discriminator `type_error`, and each such `**` is one operation at the meter. A builtin that takes names reads it too, `int`, `round`, `pow`, `complex`, `str`, `zip`, `enumerate`, `sum` and `dict`, over the count the `*` gives, and Python's order holds there as without a `*`: too many arguments in all, `int() takes at most 2 arguments (3 given)`; then a name missing or given by name and by position at that count; then the first name the callee does not take, `'x' is an invalid keyword argument for int()`; and otherwise what the call written out with that count refuses, so that with `xs` empty and `opts` holding `base`, `int(*xs, **opts)` is `int() missing string argument`. `enumerate` reads the names where they stand, over the count the `*` gives. `sorted`, `min` and `max` read it too, and count the `*` first, as Python's do: over two elements `sorted(*xs, **opts)` is `sorted expected 1 argument, got 2`, and over none `min(*xs, **opts)` is `min expected at least 1 argument, got 0`, whatever `opts` holds; then the names, counted apart from the arguments and refused under `sort`'s name at `sorted`; then a `default` beside several arguments; then a `key` that is no function. A built-in value's method that takes names, `split`, `rsplit`, `splitlines` and `expandtabs`, reads it as a builtin does, over the count the `*` gives, its receiver too where the `*` gives it; and `math.isclose` reads it in Argument Clinic's order: too many arguments in all; then too many by position, `isclose() takes exactly 2 positional arguments (3 given)`; then a name given by name and by position; then an argument missing; then a name it does not take. A list's `sort` reads it too, in the words given at a change above, and counts what the `*` gives as arguments by position once the names are merged: past two in all, `sort() takes at most 2 arguments (3 given)`, and otherwise any of them, `sort() takes no positional arguments`, before a name it does not take. A dict's `update` counts what the `*` gives, `update expected at most 1 argument, got 2`, then writes its entries and then the names, as keys, and a key that is no string is refused once those entries are written and before any name is. Through its type, where the list sorted is the first thing the `*` gives, `list.sort(*xs, **opts)` merges the names before it takes the list from the `*`, as at every method so reached: with `xs` empty, a key that is no string is `keywords must be strings` and a name given twice is `list.sort() got multiple values for keyword argument 'reverse'`, and only then is the call `unbound method list.sort() needs an argument`, or, where what the `*` gives first is no list, `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object`. What follows the list is then counted as above, before a name it does not take. With the names written and no such `**`, a first value of a type that is no list is a type_error where the unit is compiled, in the same words, and the first thing every `*` and every positional argument gives is the list, as the paragraph on a `*` at a method says. **A value bound from a call that can only refuse.** Where such a `**` leaves a call for the run to refuse, and nothing types what the call would answer, a value bound from it is a type_error where the unit is compiled, in the call's own words, whatever the mapping gives, since a bare assignment binds its name to the initializer's static type (§6.4) and such a call has none. So it is through a type's name at a receiver written of another type, where the method answers what that type holds: `v = list.pop((1, 2), **opts)` is `descriptor 'pop' for 'list' objects doesn't apply to a 'tuple' object`, and `v = dict.setdefault(5, "a", 0, **opts)` is `descriptor 'setdefault' for 'dict' objects doesn't apply to a 'int' object`. So it is at a count the callee does not take where no count it takes types the call, `v = chr("a", 2, **opts)` being `chr() takes exactly one argument (2 given)`, and where nothing types what a `*` gives: over `xs = []`, `v = list.pop(*xs, **opts)` is ``cannot infer type of binding `v` — right-hand side did not typecheck to a known type``. So it is for a change read as a value (§8.14), for a special name whose answer no value here holds (§8.20), and for an argument a callee that takes no name does not take, where what the callee answers turns on that argument or is what no value here holds, each in the words it is refused with without such a `**`: `v = abs("a", **opts)` is a type_error as `v = abs("a")` is, since `abs` answers the type it is given, and so is `v = d.get(k, **opts)`, which has no `None` to answer (§8.6). Python merges the names first, so that where `opts` holds a key that is no string each of these is `keywords must be strings`; here the mapping's words answer only where the same call is read as a statement or printed, which is refused as the program runs, as above. A handler around such a binding does not change this, since nothing has run when it is refused. Where the call fixes a type, as `str.upper(5, **opts)`, `len(xs, 2, **opts)`, `len(5, **opts)` and `s.center(1.5, **opts)` do, a value bound from it is refused as the program runs, as the call is. **A built-in namespace's call.** A `*` before a positional argument of a call into `math`, `cmath`, `vector` or `struct` (§8.16–§8.18) spreads its operand's elements across the call's positional 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, and `struct.pack(f"<{len(xs)}q", *xs)` packs every element of a list of integers `xs`. A call whose spreads are all of fixed tuples has its arguments counted at compile of the unit, as written ones are. With a spread of any other collection they are counted as the program runs, and a count the function does not take is a catchable refusal (§10.3) with discriminator `type_error`, in the words the compiler gives a call written out: `math.atan2() takes 2 positional arguments but 3 were given`. Each element must suit every parameter it could fill, as in a script's call. `math.isclose` takes `a` and `b` by name beside a `*` too, and since which of them the `*` fills is known only as the program runs, a name is placed at the count the `*` gives, as Python places it: with `xs` holding one element, `math.isclose(*xs, b=1.0)` compares it with `1.0`; with two it is `argument for isclose() given by name ('b') and position (2)`, and with none `isclose() missing required argument 'a' (pos 1)`, each a catchable refusal with discriminator `type_error`. **A built-in function's call.** A `*` before a positional argument of a built-in function spreads its operand's elements across the call's positional arguments, and every built-in function takes one: `max(*xs)`, `zip(*rows)`, `range(*bounds)`, `len(*[s])`. Every argument is evaluated, left to right, before the function runs, and the call is then the call as written with that many arguments. A count Python's function does not take is refused in the words of the `TypeError` Python raises: `abs() takes exactly one argument (2 given)`, `range expected at most 3 arguments, got 4`, `pow() missing required argument 'exp' (pos 2)`. A spread of a fixed tuple, or of a list display holding no spread, says how many arguments it gives, so its count is checked at compile of the unit: `abs(*(1, 2))` is a type_error, and any other is a catchable refusal (§10.3) with discriminator `type_error` as the program runs. So is a count written out that a spread cannot make right, whatever it holds: three arguments and a spread to `divmod`. Each element must suit what the function takes at every count it could give: at compile, the call is checked as written at each count the function takes. A count no element could suit is refused as the program runs, in the words a written call is refused in: given `xs = [1.5, 1.5]`, `round(*xs)` is a refusal naming the digit count, while `round(*[1.5])` is `2`. Where no count is suitable the call is a type_error at compile, as `abs(*["a"])` is. `max` and `min` read one argument as the collection they compare, so `max(*[5])` is the refusal `'int' object is not iterable`, in Python's words. A call's type is one type, so a function whose result's type would depend on how many arguments a spread gives is a type_error at compile saying so, where Python decides as it runs: over a `list>`, `max` with a spread gives an `int` for one list and a `list` for two. Spreads, and arguments beside them, that hold types with no common type by the list-literal rule are a type_error, since which of them stands at each position is known only as the program runs. `zip` takes one column per element, so a row's length is the program's: `zip(*rows)` is an `iterator>`. `isinstance` and `issubclass` name a class, which is no value here (§13.9), so no spread gives one and either is refused saying so. A collection with no element type yet, `xs = []` with nothing added, holds nothing, as `*[]` does. The operand is an `expression`, as in a script's call, and a spread of no collection is a type_error at its `*`, in Python's words by the call's shape: `max() argument after * must be an iterable, not int` as the call's only argument, and `Value after * must be an iterable, not int` otherwise. Each spread is one operation at the meter, and the call is the operation it is when written out (§8.3). A keyword argument beside a built-in function's `*` binds as Python binds it, written before the `*` or after it, and the call answers what it answers written out: `int(*["ff"], base=16)`, `round(*xs, ndigits=1)`, `enumerate(*xs, start=1)`, `sum(*xs, start=1)`, `pow(*xs, mod=5)`, `complex(*xs, imag=1)`, `sorted(*xs, reverse=True)`, `max(*xs, default=0)`, `zip(*rows, strict=True)` and `dict(*xs, a=1)`. A count that does not fit the keyword is refused in the words of the `TypeError` Python raises, as a count is above: over two elements, `pow(*xs, exp=2)` raises `argument for pow() given by name ('exp') and position (2)`, and `max(*xs, default=0)` raises `Cannot specify a default for max() with multiple positional arguments`. A name the function does not take is a type_error at compile, since every count would raise, where Python raises as the program runs, so no handler around the call sees it: `abs(*xs, x=1)`, and `pow(*[2], x=1)`, where Python finds the missing argument first. `str` takes no `encoding` or `errors`, since no value here is bytes. A spread of `[]` or of a list display gives a count this language reads at compile, so the refusal comes then and no handler around the call sees that either, where Python raises the same words as the program runs: `xs = []`, then `abs(*xs)`. A spread of one value that is no collection is told as the value written out is, where the function reads a finite collection: `xs = [1]`, then `sorted(*xs)`, is `sorted() requires a finite collection` as `sorted(1)` is, where Python raises `'int' object is not iterable`. A `**` before a dict display beside such a `*` gives its keys as written keyword arguments, and one before any other dict is read as above; `print` reads both. `min` and `max` with `key` alone compare the arguments, or what one argument holds, so `max(*xs, key=abs)` over one `int` raises `'int' object is not iterable`, and over a `list>` the call has no one type, as above. A `*` alone among the positional arguments is read after the keyword arguments' values, as in Python, and one beside another positional argument in its turn, so a keyword's value that changes the collection shows which. Two calls read it ahead of those values, where Python reads it after: a method's call and a call into a namespace. **A built-in method's call.** A `*` before a positional argument of a built-in method's call spreads its operand across the call's arguments, as at a built-in function's call: `"-".join(*[xs])`, `"abc".replace(*pair)`, `xs.index(2, *bounds)`. Through a type's name the first thing a spread gives may be the receiver: `str.upper(*["a"])`, `list.append(*[xs, 1])`. A list display there is read as the tuple it spells, each position of its own type. The receiver is evaluated first, then every argument left to right, and the call is then the call as written with that many arguments. A spread giving no receiver raises `unbound method str.upper() needs an argument`. A count a built-in method does not take is refused in the words of the `TypeError` Python raises, which follow how the method is written in C and not what it does: `str.upper() takes no arguments (1 given)`, `str.join() takes exactly one argument (0 given)`, `replace expected at least 2 arguments, got 1`, `find() takes at least 1 argument (0 given)`, `insert expected 2 arguments, got 1`, `pop expected at most 1 argument, got 2`. Written out, or with a spread that says how many it gives, the count is checked at compile of the unit, and with any other spread it is a catchable refusal (§10.3) with discriminator `type_error` as the program runs, counting what the spread gave. So is more written out than the method takes beside such a spread, and a suitable count an element cannot suit: each element must suit what the method takes at every count it could give, and where it suits none the call is a type_error at compile. A method's result that would have two types by count is a type_error saying so, as at a built-in function's call. A name beside such a spread binds as Python binds it once the spread's count is known, and the call answers what it answers written out: `"a b c".split(*ys, maxsplit=1)`, `"a b".split(*ys, sep=" ")`, `s.splitlines(*ys, keepends=True)`. A count that does not fit the name is refused in the words of the `TypeError` Python raises, counting the name with the positional arguments: `split() takes at most 2 arguments (3 given)` past two in all, and `argument for split() given by name ('sep') and position (1)` where the spread fills the place the name was given for. A name the method does not take, or one beside which every count would raise, is a type_error at compile, where Python raises as the program runs: `"a b".split(*ys, x=1)`, and `"a b".startswith(*ys, x=1)`, which takes no names. A `dict`'s `update` makes its names keys, as written out, and counts only what the spread gives: `d.update(*ys, k=1)`. A list's `sort` takes no positional argument, so `xs.sort(*ys, reverse=True)` sorts where `ys` gives nothing and otherwise raises `sort() takes no positional arguments`, or `sort() takes at most 2 arguments (3 given)` counting the names. A name it does not take is refused only after that count, whether the receiver is written before the call or through the type: `xs.sort(*ys, x=1)` and `list.sort(xs, *ys, x=1)` raise `sort() takes no positional arguments` where `ys` gives a value, and `'x' is an invalid keyword argument for sort()` where it gives none, the values of the names read first, and where nothing types `ys` it gives nothing (§6.3) and the second is refused where the unit is compiled, after a receiver of another type is refused for its type. A positional argument written beside the `*` is counted with what the `*` gives, before any name: `xs.sort(*ys, 5)` and `xs.sort(5, *ys)`, and the same through the type, raise `sort() takes no positional arguments` where `ys` gives one value or none, and `sort() takes at most 2 arguments (3 given)` where `ys` gives two, or one with a name beside it, the names counted; where nothing types `ys` the refusal is the compile's, in the same words. A `key=` beside the `*` is applied only where the sort runs, after the count: `xs.sort(*ys, key=len)` over a list of `int`s raises `sort() takes no positional arguments` where `ys` gives one value, and `sort() takes at most 2 arguments (3 given)` where it gives two, the key never applied; where `ys` gives none the sort runs, and a key an element refuses (`object of type 'int' has no len()`) is raised as the sort applies it, none being raised where the list has no element. Each is decided as the program runs and is catchable. Where nothing types `ys`, and where the call has no `*`, a key the elements refuse is refused where the unit is compiled. Through the type's name the first thing every `*` and every positional argument gives, in order, is the list sorted: `list.sort(*ys)`, and `list.sort(*ys, xs)`, which sorts `xs` where `ys` gives nothing and otherwise sorts the first thing `ys` gives and then raises `sort() takes no positional arguments`. Where nothing gives a first thing the call raises `unbound method list.sort() needs an argument`, before any name is read: `list.sort(*ys, x=1)` is that and not `'x' is an invalid keyword argument for sort()`; and where the first thing is no list, `descriptor 'sort' for 'list' objects doesn't apply to a 'int' object`, before what follows it is counted, as with `list.sort(*ys, 1, 2)`. What follows the list is then counted, `sort() takes no positional arguments`, or `sort() takes at most 2 arguments (3 given)` counting the names, and then the names, `sort() takes at most 2 keyword arguments (3 given)` and `'x' is an invalid keyword argument for sort()`. Where nothing types what a `*` gives it gives nothing (§6.3) and the call is refused where the unit is compiled; where a `*` is typed, as `ys[0:0]` is, it may give nothing and the refusal is the program's, a catchable refusal (§10.3). Where a `*` and the first argument written after it give values of different types, the unit cannot say what stands first and refuses it as it refuses a spread of values of different types. The spread and the names' values are read before the count is checked, as in Python, and a `key` is called only after it is checked. A display there stands in the call as written and costs nothing of its own. Every other spread is one operation at the meter, and the call is the operation it is when written out (§8.3). A spread adds no iteration to a program: it reads a finite value once, and an iterator no further than the length its source had when it was made (§8.6). So the termination guarantee (§9.5) is unaffected. ### 8.16 The `math` and `cmath` namespaces ``` import math import cmath ``` `math` and `cmath` are Python's modules of those names, provided by the language rather than stored. `import math` reads nothing from the store and runs nothing; it makes `math.` available from its line to the end of the unit, as every `import` does ([Python Reference §9.3](/ref/dke/python/#import)). `from math import sqrt, pi` binds the names it lists, as a `from` import of a stored module does, and leaves `math` itself unbound. No stored module may be named `math` or `cmath`: compiling one is refused, so each name always means its namespace. Any other name Python gives a module of its own is an ordinary module name. **`math`** | Name | Signature | Answers | |------|-----------|---------| | `math.sqrt(x)` | `N → real` | the square root of `x` | | `math.exp(x)` | `N → real` | e to the power `x` | | `math.log(x[, base])` | `N → real`; `(N, N) → real` | the natural logarithm of `x`; with `base`, the logarithm of `x` to that base, rounded once: `math.log(1000, 10)` is `3.0` | | `math.log10(x)`, `math.log2(x)` | `N → real` | the logarithm of `x` to base 10, and to base 2 | | `math.erf(x)`, `math.erfc(x)` | `N → real` | the error function of `x`, and its complement `1 - erf(x)` | | `math.gamma(x)`, `math.lgamma(x)` | `N → real` | the gamma function of `x`, and the natural logarithm of its magnitude: `math.gamma(5)` is `24.0` | | `math.sin(x)`, `math.cos(x)`, `math.tan(x)` | `N → real` | of an angle `x` in radians | | `math.asin(x)`, `math.acos(x)`, `math.atan(x)` | `N → real` | the angle in radians whose sine, cosine or tangent is `x`: from `-π/2` to `π/2` for `asin` and `atan`, and from `0` to `π` for `acos` | | `math.atan2(y, x)` | `(N, N) → real` | the angle of the point `(x, y)`, from `-π` to `π` | | `math.hypot(*coordinates)` | `(N, …) → real` | the distance from the origin to the point with these coordinates, of which there may be any number: `math.hypot(3, 4)` is `5.0`, and `math.hypot()` is `0.0` | | `math.degrees(x)`, `math.radians(x)` | `N → real` | the angle `x`, given in radians, in degrees; and `x`, given in degrees, in radians | | `math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)` | `(N, N) → bool` | whether `a` and `b` differ by at most `rel_tol` times the larger of their magnitudes, or by at most `abs_tol` | | `math.floor(x)`, `math.ceil(x)` | `N → int` | the greatest integer no larger than `x`, and the least no smaller; an `int` is its own, and a `bool` the `int` it is | | `math.trunc(x)` | `N → int` | the integer nearest zero, with the same rules for an `int`, a `bool` and a `real` past what an `int` holds | | `math.sinh(x)`, `math.cosh(x)`, `math.tanh(x)` | `N → real` | the hyperbolic sine, cosine and tangent of `x` | | `math.asinh(x)`, `math.acosh(x)`, `math.atanh(x)` | `N → real` | the inverse hyperbolic sine, cosine (for `x` of 1 or more) and tangent (for `x` between -1 and 1, exclusive) | | `math.cbrt(x)` | `N → real` | the cube root of `x`, a negative `x` included | | `math.exp2(x)` | `N → real` | 2 to the power `x` | | `math.expm1(x)`, `math.log1p(x)` | `N → real` | e to the power `x`, less 1; and the natural logarithm of 1 plus `x` (for `x` above -1), each exact where `x` is near zero | | `math.pow(x, y)` | `(N, N) → real` | `x` to the power `y`, always a `real`: `math.pow(2, 10)` is `1024.0` | | `math.fabs(x)` | `N → real` | the magnitude of `x`, as a `real` | | `math.copysign(x, y)` | `(N, N) → real` | `x` with the sign of `y`, a zero's sign included: `math.copysign(1, -0.0)` is `-1.0` | | `math.fmod(x, y)`, `math.remainder(x, y)` | `(N, N) → real` | the remainder of `x` divided by `y` with the sign of `x`; and `x` less `y` times the whole number nearest `x / y`, a tie going to the even one | | `math.modf(x)` | `N → tuple` | the fractional and the whole part of `x`, each with `x`'s sign: `math.modf(-2.5)` is `(-0.5, -2.0)` | | `math.frexp(x)` | `N → tuple` | the mantissa and exponent of `x`, `x` being the mantissa times 2 to the exponent and the mantissa's magnitude at least 0.5 and under 1 (or zero): `math.frexp(8.0)` is `(0.5, 4)` | | `math.ldexp(x, i)` | `(N, I) → real` | `x` times 2 to the power `i`, an `int` | | `math.nextafter(x, y)` | `(N, N) → real` | the `real` after `x` in the direction of `y`, `y` itself where they are equal | | `math.ulp(x)` | `N → real` | the gap between the magnitude of `x` and the next `real`; for the largest `real`, the gap below it | | `math.gcd(*integers)`, `math.lcm(*integers)` | `(I, …) → int` | the greatest common divisor and the least common multiple of any number of `int`s, none included: `math.gcd()` is `0`, `math.lcm()` is `1`, and a zero among them makes `lcm` `0` | | `math.comb(n, k)` | `(I, I) → int` | the number of ways to choose `k` of `n`, `0` where `k` is past `n` | | `math.perm(n[, k])` | `I → int`; `(I, I) → int` | the number of ordered choices of `k` of `n`, `0` where `k` is past `n`; with one argument, `k` is `n`, which Python writes as `k=None` and DKE Python, which has no `None` (§3.5.4), as no `k` | | `math.factorial(n)` | `I → int` | the product of the whole numbers from 1 to `n` | | `math.isqrt(n)` | `I → int` | the greatest `int` whose square is no larger than `n` | | `math.fsum(iterable)` | `collection → real` | the sum of the elements, each taken exactly: `math.fsum([0.1] * 10)` is `1.0` | | `math.prod(iterable, *, start=1)` | `collection → N` or `complex` | the product of `start` and the elements: an `int` where each is an `int`, a `complex` where one is, and a `real` otherwise | | `math.dist(p, q)` | `(collection, collection) → real` | the distance between the points `p` and `q`, of one length: `math.dist([1, 2], [4, 6])` is `5.0` | | `math.isinf(x)`, `math.isnan(x)`, `math.isfinite(x)` | `N → bool` | whether `x` is an infinity, a NaN, or neither; an `int` and a `bool` are finite | | `math.pi`, `math.e`, `math.tau` | `real` | the doubles nearest π, e and 2π | | `math.inf`, `math.nan` | `real` | the positive infinity and the NaN (§3.5.7) | **`cmath`** | Name | Signature | Answers | |------|-----------|---------| | `cmath.exp(z)` | `C → complex` | e to the power `z` | | `cmath.sqrt(z)` | `C → complex` | the principal square root of `z` | | `cmath.phase(z)` | `C → real` | the argument of `z`, from `-π` to `π` | | `cmath.polar(z)` | `C → tuple` | `(abs(z), phase(z))` | | `cmath.rect(r, phi)` | `(N, N) → complex` | the complex of modulus `r` and argument `phi` | | `cmath.isinf(z)`, `cmath.isnan(z)`, `cmath.isfinite(z)` | `C → bool` | whether a part of `z` is an infinity, whether a part is a NaN, and whether both parts are finite: `cmath.isinf(complex(math.inf, math.nan))` and `cmath.isnan` of it are both `True` | | `cmath.pi`, `cmath.e`, `cmath.tau`, `cmath.inf`, `cmath.nan` | `real` | as in `math` | | `cmath.infj`, `cmath.nanj` | `complex` | the complex with a zero real part and an imaginary part that is `inf`, and `nan` | `N` is the numeric operand set of §8.3, `{int, real, bool}`, `I` is the integer set `{int, bool}`, and `C` adds `complex` to `N`. A `math` function given a `complex`, bar `math.prod`, which multiplies one, and any function given a value that is not a number, is a `type_error`, where Python raises `TypeError`. A function of `I` given a `real` is a `type_error` in Python's words, `'float' object cannot be interpreted as an integer`, refused where the unit is compiled wherever the argument is a `real`; `math.ldexp`'s exponent, given one, is `Expected an int as second argument to ldexp.`. The floor, ceiling or truncation of a `real` past what an `int` holds is a runtime refuse (§10.3), as `round`'s is, where Python answers a longer integer, and so is an `int` answer of `gcd`, `lcm`, `comb`, `perm`, `factorial` or `prod` that is past what an `int` holds, as any such `int` is (§8.3): `math.factorial(21)`. Every intermediate step of them is exact, so an answer that fits is never refused for a step that did not. Only `math.isclose` and `math.prod` take keyword arguments: `rel_tol` and `abs_tol` by name only, and `a` and `b` by position or by name, for the first; `start` by name only, for the second, whose count Python words as `prod() takes exactly 1 positional argument (2 given)` and `prod() takes at most 2 arguments (3 given)`. `math.hypot`, `math.gcd` and `math.lcm` take any number of arguments, none included, so no count is refused. A call with more or fewer arguments than the function takes is a `type_error` at the function's name, in the words of the `TypeError` Python raises, checked where the unit is compiled: `math.sqrt() takes exactly one argument (2 given)`, `atan2 expected 2 arguments, got 1`, `math.log requires 1 to 2 arguments`, `isclose() missing required argument 'b' (pos 2)`, `isclose() takes exactly 2 positional arguments (3 given)`, and `isclose() takes at most 4 keyword arguments (5 given)` where every argument is named. A keyword the function does not take, a constant called (`math.pi()`), a function used as a value (`f = math.sqrt`, §8.8), a name the namespace does not have, a namespace used without its `import`, and a call on a line of its own, whose value would go nowhere, are each a `parse_error`. **Results.** Each function follows Python's own algorithm for it (CPython 3.11), and each power, logarithm, exponential, trigonometric function, inverse trigonometric function and modulus that algorithm uses is correctly rounded, as in §8.3. So a result is the same wherever it is computed, and departs from Python's only where Python's platform rounds one of those steps wrongly: `math.atan2(-31.77378470442932, -2.721897422427844)` is `-1.6562525739043301`, the exact angle rounded, where Python on a platform that rounds this one wrongly prints `-1.65625257390433`. `log10`, `log2`, `erf`, `erfc`, `gamma` and `lgamma` are likewise the exact value rounded once, where Python's platform, or for `gamma` Python's own approximation, may differ in the last digit, and so are `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `cbrt`, `exp2`, `expm1`, `log1p` and `pow`: `math.cbrt(576.7985282082266)` is `8.32417843718322`, where Python on a platform that rounds it wrongly prints `8.324178437183217`, and `math.sinh(5.097328896223573)` is `81.78912966062903`, where such a platform prints `81.78912966062904`. `fabs`, `copysign`, `fmod`, `remainder`, `trunc`, `modf`, `frexp`, `ldexp`, `nextafter` and `ulp` are exact on every platform, and the integer functions are exact. Ten functions depart from Python's algorithm, because it rounds where the answer need not be: - `math.degrees(x)` is `x` times 180/π and `math.radians(x)` is `x` times π/180, each exact product rounded once. Python multiplies by the constant already rounded to a `real` and rounds again, which gives another last digit in about one result in five for `degrees` and one in ten for `radians`: `math.degrees(-880.7976600675347)` is `-50465.9885268683`, where Python answers `-50465.98852686831`. - `math.log(x, base)` is the logarithm of `x` to `base` rounded once. Python divides the natural logarithm of `x` by that of `base`, each already rounded to a `real`, and rounds the quotient again, which gives another last digit in about one result in three: `math.log(1000, 10)` is `3.0`, where Python answers `2.9999999999999996`, and `math.log(125, 5)` is `3.0`, where Python answers `3.0000000000000004`. Wherever the exact logarithm is a `real`, as it is for every power of the base, that is the answer. In the rare case where the exact quotient lies so near the midpoint of two `real`s that the result could round either way, the call is not proven to round it correctly, and no such case is known. - `math.hypot` is the distance correctly rounded, of any number of coordinates: the exact sum of their squares, whose square root is rounded once. Python's algorithm for three or more is not proven to round correctly. So `math.hypot(*p)` and `math.dist(p, [0] * len(p))` are one answer, and an `int` coordinate is its own exact value where Python first rounds it to a `real`. - `math.fsum(xs)` is the exact sum of the elements rounded once, an `int` taken as the exact `int` it is, and a sum of nothing, or of terms that cancel, is `0.0`. Python's sum is exact too, but raises `OverflowError: intermediate overflow in fsum` where a partial sum passes the largest `real`, though the whole is a `real`: `math.fsum([1e308, 1e308, -1e308])` is `1e+308`. A sum past the largest `real` is `inf` or `-inf`, where Python raises the same `OverflowError`. Python rounds an `int` element to a `real` first, which gives another last digit: `math.fsum([2 ** 53 + 1, 1, 1])` is `9007199254740996.0`, where Python answers `9007199254740994.0`. A sum that lies exactly half-way between two `real`s is the one with the even last digit: `math.fsum([1.0, 2 ** -53])` is `1.0`. - `math.prod(xs)` is the exact product of `start` and the elements rounded once, each part of a `complex` product likewise. Python rounds after every factor, which gives another last digit: `math.prod([-2.6, 1.9, -4.0, -2.5])` is `-49.4`, where Python answers `-49.39999999999999`. A product whose factors pass through a `real` beyond range, or a zero, is the exact product: `math.prod([1e200, 1e200, 0.0])` is `0.0`, where Python answers `nan` because its partial product was `inf`; a product itself past the largest `real` is `inf` or `-inf`, as Python answers. Python multiplies whatever `*` takes, `math.prod(['ab'], start=2)` being `'abab'`; DKE Python takes numbers, and anything else is a `type_error`. A product of `int`s is an `int`, with no step refused that does not matter to the answer (`math.prod([2 ** 40, 2 ** 40, 0])` is `0`). - `math.dist(p, q)` is the exact distance rounded once, from the exact differences of the coordinates. Python rounds each difference, which gives another last digit: `math.dist([-1.7, 1.6, -9.6], [2.3, 2.6, -8.8])` is `4.199999999999999`, where Python answers `4.2`. A distance past the largest `real` is `inf`, as Python answers. - `cmath.exp`, `cmath.rect` and `cmath.sqrt` answer each part of the result, the real and the imaginary, as its exact value rounded once. Python composes parts it has already rounded, e to the power of the real part times the cosine of the imaginary part, the modulus times the cosine of the angle, a square root of a sum that holds another, and rounds again, which gives another last digit in about three calls in ten: `cmath.exp(complex(1.3, 2.7)).imag` is `1.5681835703495033`, where Python answers `1.5681835703495035`; `cmath.rect(2.5, 0.7).real` is `1.912105468211221`, where Python answers `1.9121054682112213`; and `cmath.sqrt(complex(3, 7)).imag` is `1.5191729832155239`, where Python answers `1.5191729832155236`. Where Python gives a part its own way, the part is Python's: the imaginary part of `cmath.exp` of a real is that real's, signed zero and all, a zero angle makes `cmath.rect(r, phi)` `complex(r, r * phi)`, and a zero part of a product takes the product's sign. In the rare case where an exact part lies so near the midpoint of two `real`s that it could round either way, the call is not proven to round it correctly, and no such case is known. `math.degrees(math.asin(0.5))` is `30.000000000000004`, which is no error of either function: π/6 has no exact `real`, `math.asin(0.5)` is the nearest one to it, and `30.000000000000004` is the nearest one to that `real` times 180/π. The sign of a zero follows Python: `math.atan2(-0.0, -1.0)` is `-3.141592653589793`, `math.asin(-0.0)` is `-0.0`, `cmath.phase(-1.0)` is `3.141592653589793`, and `cmath.sqrt(complex(-4.0, -0.0))` is `-2j`. **Infinity and NaN.** Each function takes an infinity or a NaN and answers IEEE 754's value, as Python does where it answers: `math.exp(math.inf)` is `inf`, `math.atan(math.inf)` is `1.5707963267948966`, `math.fsum([math.nan, 1.0])` is `nan`, and `cmath.exp(complex(-math.inf, 1))` is `0j`. `math.isinf`, `math.isnan` and `math.isfinite` tell them apart. DKE Python departs where Python raises instead of answering, as §8.3's *Infinity and NaN* rules, and at the sign of a NaN: | Case | DKE Python | Python 3.11 | |---|---|---| | a result past the largest `real` from finite arguments: `math.exp(1000)`, `math.sinh(711)`, `math.cosh(711)`, `math.expm1(710)`, `math.exp2(1024)`, `math.ldexp(1.0, 1024)`, `math.pow(10, 400)`, `math.gamma(172)`, `math.lgamma(1e306)`, `math.fsum([1e308, 1e308])` | `inf` | `OverflowError` | | the same in `cmath`: `cmath.exp(800)`, `cmath.exp(complex(710, 0.5))`, `cmath.polar(complex(1.7e308, 1.7e308))` | `(inf+0j)`, `(inf+1.0710341440060488e+308j)`, `(inf, 0.7853981633974483)` | `OverflowError` | | an infinite argument with no value: `math.fmod(math.inf, 2.0)`, `math.remainder(math.inf, 2.0)`, `math.sin`, `math.cos` and `math.tan` of an infinity, `math.asin(math.inf)`, `math.acos(-math.inf)`, `math.atanh(math.inf)`, and `math.fsum([math.inf, -math.inf])` | `nan` | `ValueError` | | `-inf` to a function defined only from some point upward: `math.sqrt`, `math.log`, `math.log10`, `math.log2`, `math.log1p`, `math.acosh` and `math.gamma` of `-math.inf` | `nan` | `ValueError` | | the sign of a NaN: `math.copysign(1.0, -math.nan)` | `1.0`: there is one NaN, and it has no sign (§3.5.7) | `-1.0` | With finite arguments, an argument outside the function's domain still refuses, as in Python (below). **Faults.** A call refuses at run time where Python raises, with Python's message (§10.3): | Cause | Discriminator | Message | |-------|---------------|---------| | an argument outside the function's domain: `math.sqrt(-1)`, `math.log(0)`, `math.log10(0)`, `math.asin(2)`, `math.acosh(0.5)`, `math.atanh(1)`, `math.log1p(-1)`, `math.fmod(1, 0)`, `math.remainder(1, 0)`, `math.pow(0, -1)`, `math.pow(-8, 0.5)`, and a pole of the gamma function, `math.gamma(0)`, `math.gamma(-1)`, `math.lgamma(-3)` | `value_error` | `math domain error` | | a negative argument to the counting functions: `math.comb(-1, 2)`, `math.perm(2, -1)`, `math.factorial(-1)`, `math.isqrt(-1)`; and `math.dist` given points of two lengths | `value_error` | `n must be a non-negative integer`, `k must be a non-negative integer`, `factorial() not defined for negative values`, `isqrt() argument must be nonnegative`, `both points must have the same number of dimensions` | | an `int` answer past what an `int` holds: `math.factorial(21)`, `math.gcd(-2 ** 63)`, `math.trunc(1e19)` | `arithmetic_overflow` | ``integer overflow: `math.factorial` result exceeds the signed 64-bit range``, with the function's name, as §8.3 refuses an `int` past its range | | an infinity or a NaN to `math.floor`, `math.ceil` or `math.trunc`, which no `int` is | `arithmetic_overflow` for an infinity, `value_error` for a NaN | `cannot convert float infinity to integer`, `cannot convert float NaN to integer` | | a base of `1` to `math.log` | `division_by_zero` | `float division by zero` | | a negative tolerance to `math.isclose` | `value_error` | `tolerances must be non-negative` | A result too small to represent is the nearest number that is, zero included, and no fault: `math.exp(-1000)` is `0.0`, as in Python. A constant is the literal it names, so it costs nothing at the meter, as a literal does. A function call is one operation, as every built-in call is. ### 8.17 The `vector` namespace ``` import vector ``` `vector` is provided by the language, as `math` and `cmath` are, and is imported and reserved as they are (§8.16): `import vector` reads nothing from the store, and no stored module may be named `vector`. Python has no module of this name. The name is reserved all the same so that `import vector` names the same namespace in every store: a module written against it cannot be changed by what another module in its store is called. Each of its two functions is defined below as a Python function, and answers what that function answers. | Name | Signature | Answers | |------|-----------|---------| | `vector.apply(xs, m, axes)` | `(list, list>, list) → list` | `xs` with the square matrix `m` applied along the axes `axes` | | `vector.sumsq(xs, axes)` | `(list, list) → list` | the sum of the squared parts of the elements of `xs`, for each pattern of the axes `axes` | `C` is the operand set of §8.16, `{int, real, bool, complex}`. **Shape.** `xs` holds 2ⁿ numbers for some n ≥ 0, and axis `a` is bit `a` of an index into it, so axis 0 is the lowest bit. `axes` names k different axes, each from 0 to n − 1, and `m` is 2ᵏ rows of 2ᵏ numbers. The first axis named is the highest bit of a row or column of `m`: with `axes` `[a, b]`, row 2 of `m` is the element whose bit `a` is 1 and whose bit `b` is 0. With no axes, `m` is `[[c]]` and every element is multiplied by `c`. **Result.** The answer is the list this Python function returns, element for element and class for class, signed zeros included: ```python def apply(xs, m, axes): k = len(axes) off = [sum(1 << a for t, a in enumerate(axes) if j >> (k - 1 - t) & 1) for j in range(1 << k)] out = list(xs) for base in range(len(xs)): if any(base >> a & 1 for a in axes): continue for r in range(1 << k): acc = m[r][0] * xs[base | off[0]] for c in range(1, 1 << k): acc = acc + m[r][c] * xs[base | off[c]] out[base | off[r]] = acc return out ``` Each `*` and `+` there is the operator of §8.3, so a row is its products added left to right from the first, rounded as each step rounds, and an element keeps the class its arithmetic gives: `vector.apply([1.0, 2.0], [[1, 0], [1j, 1]], [0])` is `[1.0, (2+1j)]`. The result's type is `list` where `xs` or `m` holds `complex`, and otherwise the type `*` gives an element of each. An argument of another type is a `type_error`. A wrong number of arguments is a `type_error` too, as in §8.16, in the words Python gives a function written in Python: `vector.apply() missing 2 required positional arguments: 'm' and 'axes'` and `vector.apply() takes 3 positional arguments but 4 were given`. A function used as a value, a namespace used without its `import`, and a call on a line of its own are each a `parse_error`, as in §8.16. **Faults.** A call refuses at run time, checking in the order of this table, and an axis's two checks in the order `axes` names it: | Cause | Discriminator | Message | |-------|---------------|---------| | `len(xs)` is not a power of two, `0` included | `value_error` | `vector.apply() needs a vector whose length is a power of two, and 3 is not one` | | an axis outside 0 to n − 1 | `value_error` | `vector.apply(): axis 2 is out of range for a vector of 4 elements` | | an axis named twice | `value_error` | `vector.apply(): axis 1 is named twice` | | `m` is not 2ᵏ rows of 2ᵏ | `value_error` | `vector.apply(): the axes named take a 2 × 2 matrix` | | an `int` product or sum past the signed 64-bit range, as §8.3 refuses it | `arithmetic_overflow` | `integer overflow: `vector.apply` result exceeds the signed 64-bit range` | A call is one operation, whatever the length of `xs`, as every built-in call is. Its result is built in full, so its elements count toward any limit a service sets on how much one run builds, as the elements of `[0] * n` do. **`vector.sumsq`.** The companion to `vector.apply`: where `apply` carries a matrix along axes, `sumsq` adds along them, one sum for each pattern the named axes can make. **Shape.** `xs` holds 2ⁿ numbers for some n ≥ 0 and axis `a` is bit `a` of an index into it, as for `apply`, and `axes` names k different axes, each from 0 to n − 1. The first axis named is the highest bit of a pattern: with `axes` `[a, b]`, pattern 2 is the elements whose bit `a` is 1 and whose bit `b` is 0. With no axes there is one pattern and every element is in it. The answer holds the 2ᵏ sums, the first for the pattern 0. **Result.** The answer is the list this Python function returns, element for element, signed zeros included: ```python def sumsq(xs, axes): k = len(axes) parts = [[] for _ in range(1 << k)] for i, x in enumerate(xs): j = 0 for a in axes: j = j << 1 | (i >> a & 1) z = complex(x) parts[j] += [z.real * z.real, z.imag * z.imag] return [fsum(p) for p in parts] def fsum(p): try: return math.fsum(p) except OverflowError: return math.inf ``` Each element is read as the complex number `complex(x)` is, so an `int` or a `bool` is its real part with a zero imaginary part, and each sum is `math.fsum`'s: exactly rounded once, whatever the order of what it adds. A sum past the range of a real is `inf`, a square past it (`1e200 * 1e200`) is `inf` already, and an element with a part that is `nan` makes its sum `nan`, as §8.16's `math.fsum` answers each. `vector.sumsq([3, 4], [])` is `[25.0]`, `vector.sumsq([1, 2, 3, 4], [0])` is `[10.0, 20.0]`, and `vector.sumsq([1, 2, 3, 4], [1, 0])` is `[1.0, 4.0, 9.0, 16.0]` where `[0, 1]` makes `[1.0, 9.0, 4.0, 16.0]`. Every sum is a `real` whatever the numbers that make it, and the result's type is `list`. An argument of another type is a `type_error`, and so is a wrong number of arguments, in the words Python gives a function written in Python: `vector.sumsq() missing 1 required positional argument: 'axes'` and `vector.sumsq() takes 2 positional arguments but 3 were given`. A function used as a value, a namespace used without its `import`, and a call on a line of its own are each a `parse_error`, as in §8.16. **Faults.** A call refuses at run time, checking in the order of this table, and an axis's two checks in the order `axes` names it; the first three are `vector.apply`'s, with `vector.sumsq` for its name: | Cause | Discriminator | Message | |-------|---------------|---------| | `len(xs)` is not a power of two, `0` included | `value_error` | `vector.sumsq() needs a vector whose length is a power of two, and 3 is not one` | | an axis outside 0 to n − 1 | `value_error` | `vector.sumsq(): axis 2 is out of range for a vector of 4 elements` | | an axis named twice | `value_error` | `vector.sumsq(): axis 1 is named twice` | The Python function does not refuse an axis out of range or named twice, which it reads as a bit that is never set or set again; the call refuses both, as `apply` does. Nothing else refuses: `vector.sumsq([1e200, 1.0], [0])` is `[inf, 1.0]`. A call is one operation, whatever the length of `xs`, as every built-in call is. Its result is the 2ᵏ sums and nothing more, so only those elements count toward any limit a service sets on how much one run builds, as the elements of `[0] * n` do, and the length of `xs` adds nothing to them. ### 8.18 The `struct` namespace ``` import struct ``` `struct` is Python's module of that name, provided by the language, and is imported and reserved as `math` is (§8.16): `import struct` reads nothing from the store, and no stored module may be named `struct`. It lays numbers out as the bytes of a blob (§3.5.8), and reads them back, by a **format** string. Each call answers what Python's `struct` answers, in Python's words where it refuses, except where a value is one DKE does not hold (below). | Name | Signature | Answers | |------|-----------|---------| | `struct.pack(format, v1, v2, …)` | `(string, …) → blob` | the values laid out as the format describes | | `struct.unpack(format, buffer)` | `(string, blob) → tuple` | the values the format reads from `buffer`, in order | | `struct.calcsize(format)` | `string → int` | how many bytes the format lays out | **The format.** An optional first character chooses the byte order, the sizes and the alignment. Codes follow, each after an optional decimal count, and whitespace between codes is skipped. | First character | Byte order | Sizes | Alignment | |-----------------|------------|-------|-----------| | `@`, or none | little-endian | native | native | | `=` | little-endian | standard | none | | `<` | little-endian | standard | none | | `>` or `!` | big-endian | standard | none | Native layout is the one Python gives on a 64-bit x86 Linux host, wherever the program runs, so a format lays out the same bytes everywhere: with `@`, a code is preceded by the zero bytes that bring its offset to a multiple of its alignment. | Code | Packs | Unpacks to | Standard size | Native size, alignment | |------|-------|------------|---------------|------------------------| | `x` | no value, as a zero byte | no value | 1 | 1, 1 | | `c` | a blob of one byte | a blob of one byte | 1 | 1, 1 | | `b`, `B` | `I` | `int` | 1 | 1, 1 | | `?` | any value, by its truth | `bool` | 1 | 1, 1 | | `h`, `H` | `I` | `int` | 2 | 2, 2 | | `i`, `I` | `I` | `int` | 4 | 4, 4 | | `l`, `L` | `I` | `int` | 4 | 8, 8 | | `q`, `Q` | `I` | `int` | 8 | 8, 8 | | `n`, `N`, `P` | `I` | `int` | none | 8, 8 | | `e` | `N` | `real` | 2 | 2, 2 | | `f` | `N` | `real` | 4 | 4, 4 | | `d` | `N` | `real` | 8 | 8, 8 | | `F` | `C` | `complex` | 8 | 8, 4 | | `D` | `C` | `complex` | 16 | 16, 8 | | `s` | a blob | a blob | 1 | 1, 1 | | `p` | a blob | a blob | 1 | 1, 1 | `I` is `{int, bool}`, and `N` and `C` are the operand sets of §8.16. A lowercase integer code is signed and its uppercase twin unsigned. `e`, `f` and `d` are IEEE 754 binary16, binary32 and binary64, and `F` and `D` a complex as two of `f` and two of `d`, the real part first; the two complex codes are Python 3.14's. `n`, `N` and `P` have no standard size, so after `=`, `<`, `>` or `!` they are not codes. A count before a code repeats it, so `"3h"` lays out what `"hhh"` does, except before `s` and `p`, where it is the length of one blob: `"5s"` packs one blob into five bytes, cut short or padded with zero bytes, and unpacks five bytes as one blob. `p` writes the length it keeps in its first byte, at most 255, then that many bytes, and unpacks the length its first byte gives, at most its count less one. A count of 0 lays out no item, except `0s` and `0p`, which take a blob, lay out none of it and unpack an empty one, as Python 3.14 does. **Types.** `pack` gives a `blob` and `calcsize` an `int`. The tuple `unpack` gives is typed from its format where the call writes it, and a pad byte, which gives no value, is not counted: - A string literal: a fixed tuple of each item's type, in order, where it lays out 256 items or fewer, so `struct.unpack("`. Past 256, a format whose codes all unpack to one type `T` gives a `tuple`, and any other a `tuple`. - An f-string whose every hole is an `int`, and whose text's codes all unpack to one type `T`: a `tuple`, so `struct.unpack(f"<{n}D", b)` is a `tuple`. - Any other format: a `tuple`, each element of which is read as §5.6 reads a `value`. A format that is not a `string` and a buffer that is not a `blob` are each a `type_error`, in these words: `struct.pack() takes its format as a string, not int` and `struct.unpack() reads a blob, not string`. A wrong number of arguments is a `type_error` in Python's words, as in §8.16: `unpack expected 2 arguments, got 1`, `missing format argument` for a `pack` given none, and `_struct.calcsize() takes exactly one argument (2 given)`. A function used as a value, a namespace used without its `import`, and a call on a line of its own are each a `parse_error`, as in §8.16. A `*` argument spreads into a call (§8.15). **Faults.** A call refuses at run time where Python raises. It reads the whole format first, then counts the values, then packs each one left to right, so the fault it meets first is the one it refuses with: | Cause | Discriminator | Message | |-------|---------------|---------| | a format character outside ASCII | `value_error` | `'ascii' codec can't encode character '\xe9' in position 1: ordinal not in range(128)` | | a character that is not a code | `value_error` | `bad char in struct format` | | a count with no code after it | `value_error` | `repeat count given without format specifier` | | a format laying out more bytes than an `int` holds | `value_error` | `total struct size too long` | | `pack` given more or fewer values than the format lays out | `value_error` | `pack expected 2 items for packing (got 1)` | | `unpack` given a blob of another length | `value_error` | `unpack requires a buffer of 4 bytes` | | a value the code does not pack | `value_error` | `required argument is not an integer`, and `… not a float`, `… not a complex`, `char format requires a bytes object of length 1` and `argument for 's' must be a bytes object` | | an `int` outside the code's range | `value_error` | `byte format requires -128 <= number <= 127`, and its forms below | | a number too large for `e`, `f` or `F` | `arithmetic_overflow` | `float too large to pack with f format` | An `int` outside a code's range is refused in Python's words, and Python's differ by byte order as well as by code: `struct.pack("h", 32768)` with `'h' format requires -32768 <= number <= 32767`, and `struct.pack("Q", -1)` with `int too large to convert`. **Values DKE does not hold.** An `int` is at most 2⁶³ − 1 (§5.1). So `unpack` refuses one answer Python gives: an unsigned integer above 2⁶³ − 1, with `arithmetic_overflow` and `integer overflow: `struct.unpack` result exceeds the signed 64-bit range`. **Infinities and NaN.** A `real`, and a part of a `complex`, may be an infinity or a NaN (§3.5.7), and each packs to its IEEE 754 encoding and unpacks from it, as in Python. There is one NaN: `unpack` reads every NaN bit pattern as it, and `pack` writes it as the quiet NaN with its sign bit clear, `7ff8000000000000` for `d`, `7fc00000` for `f` and `7e00` for `e`, most significant byte first. Python writes the bits of the NaN it was given, so where arithmetic made the NaN (`inf - inf`), Python on x86-64 writes `fff8000000000000` and DKE Python `7ff8000000000000`. A finite value too large for `e`, `f` or `F` is still refused as above; an infinity packs. A call is one operation, however many values it packs or reads, as every built-in call is, and a `*` argument is one more (§8.15). The values `unpack` gives and the bytes of the blob `pack` builds count toward any budget a service sets on how much a run builds (§9.5), as a list's elements and a string's characters do. --- ### 8.19 Named expressions ``` named-expr := IDENT ':=' expr ``` A **named expression** binds the name to the value of `expr` when it is evaluated, and answers that value: `if (n := len(xs)) > 2:`, `print(a := 1, a)`, `[y for x in xs if (y := x * 2) > 2]`. It stands in place of an `expr` where Python's grammar reads one: in parentheses, as an element of a display or a comprehension, as an argument of a call, and as the whole condition of an `if`, an `elif` or a `case`'s guard, or the whole subject of a `match`. Anywhere else the `:=` is a parse_error where it stands (§10.1), and its target MUST be a name. ``` # running_total.dpy — a running total, bound as each element is evaluated def running() -> list: total = 0 return [total := total + x for x in [1, 2, 3]] ``` It binds as an assignment does (§6.1), so the name keeps its one type through it. It binds when it is evaluated, so what is evaluated before it reads the old value and what is evaluated after it reads the new one: `print(n, (n := 2), n)` prints the old value and then `2` twice. What a conditional expression, an `and` or an `or` does not evaluate binds nothing, and a later read of a name nothing else bound is a catchable refuse (§10). In a comprehension it binds in the scope that holds the comprehension, and the name keeps its last value after it. A comprehension's variable cannot be rebound by one, and a comprehension's iterable cannot hold one, as Python refuses. It is a binding, so it costs no operation. Its value cannot be a read of the store, which needs a binding of its own (§7.4). A method whose body holds one is called as its stored script, so the name stays the method's own. A rule's condition and conclusion hold none (§14). ### 8.20 Special names on a built-in value A **special name** begins and ends with two underscores. A value of a type Python has holds the special names its Python class holds, and one called by name answers as the spelling it stands for: `xs.__len__()` is `len(xs)`, `a.__add__(b)` is `a + b`, `a.__lt__(b)` is `a < b`, `x.__abs__()` is `abs(x)`, `xs.__getitem__(k)` is `xs[k]`, `xs.__contains__(v)` is `v in xs`, `x.__str__()` is `str(x)`, `x.__floor__()` is `math.floor(x)` and `xs.__iter__()` is `iter(xs)`. So it is for every operator, comparison, conversion and built-in a special name stands for. A reflected name takes its operands the other way round: `(2).__rpow__(10)` is `10 ** 2`. A call costs what its spelling costs. ``` # special_names.dpy — a special name answers as the spelling it stands for def show(): print([3, 1].__len__()) # 2 print((2).__rsub__(10)) # 8 print("ab".__mul__(2)) # abab print([1, 2].__contains__(2)) # True ``` A special name the value's class does not hold is a type_error in Python's words, `'int' object has no attribute '__len__'`. So are a count of arguments its Python method does not take, `expected 0 arguments, got 1`, and a keyword argument, `wrapper __len__() takes no keyword arguments`. **A `*` or a `**` at a special name.** A `*` (§8.15) spreads its operand across a special name's arguments, and a `**` gives it names, as at any method. The name is looked up where the unit is compiled, as Python looks it up before it reads an argument. A `**` display's names are refused there too, in Python's order: a name given twice; at a method, a key that is no string, `keywords must be strings`; then any name, a slot's method refusing a key that is no string as a name. Otherwise the call is the program's as it runs, in Python's words for a call that unpacks, which calls the method bound to its receiver. Each `*` is read where it stands; the names a `**` before any dict gives are merged, a name given twice naming a slot's method by the class that defines it, `int.__add__() got multiple values for keyword argument 'x'` on a `bool`, and any other method by its receiver's class, `bool.__floor__() got multiple values for keyword argument 'x'`; then any name is refused, by a slot's method without reading a key, `wrapper __len__() takes no keyword arguments`, and by any other once no key is other than a string, `float.__floor__() takes no keyword arguments`; and then the count, `expected 1 argument, got 0`, or `bool.__floor__() takes no arguments (1 given)` where a call written out names `int.__floor__()`. An unhashable class's `__hash__`, which is `None`, merges the names, `None got multiple values for keyword argument 'x'`, refuses none, and is refused, `'NoneType' object is not callable`. At each count the name takes, the call answers its spelling, and the call has the type every such count agrees on, as at a method (§8.15); where no count a `*` may give answers, the call is a type_error where the unit is compiled, in the words of the first that does not. With `xs` holding one element, `print(n.__abs__(*xs))` is `expected 0 arguments, got 1` as the program runs. Where Python's answer is one no value here holds, the call is a type_error saying what Python answers: - An operator's special name given an operand its class does not take answers `NotImplemented` in Python, so that the operator asks the other operand. `(1).__add__("a")` is refused, naming `+`. Given a `value` (§5.6), the call is refused when the `value` may hold a class it would answer `NotImplemented` for, since which it answers cannot be read before the program runs. - `__hash__` on a class that cannot be hashed (§5.1) is `None` in Python, and the call is refused as Python refuses it, `'NoneType' object is not callable`. On one that can, it answers what `hash()` does, which DKE Python does not have. - `__class__` reads the class, and a class is not a value. `isinstance` tests it (§13.9). - A special name read with no call is a method, which is not a value (§8.8). - The machinery Python keeps behind a value, `__init__`, `__new__`, `__sizeof__`, `__reduce__`, `__getattribute__`, `__setattr__`, `__doc__` and the rest, is refused. A value here carries none of it. Beside a `**` before a dict whose keys only the program knows, wherever the value is bound to no name, a refusal in the list above that Python reaches only after the names waits for them: the names are merged and refused as above, the count is checked, and the call is then refused in the same words, as the program runs. With `opts` empty, `print((1).__add__("a", **opts))` is refused naming `+`, and with `opts` holding `x`, `wrapper __add__() takes no keyword arguments`. So it is for `__hash__`. A value bound from such a call is a type_error where the unit is compiled, whatever the mapping gives (§8.15). `__class__` and the machinery are looked up, and refused, where the unit is compiled, whatever the mapping gives. A special name that changes its receiver is a statement, as the method it stands for is (§8.14). `xs.__setitem__(k, v)` is `xs[k] = v`, `xs.__delitem__(k)` is `del xs[k]`, and an in-place name is its augmented assignment: `xs.__iadd__(ys)` is `xs += ys` and `s.__ior__(t)` is `s |= t`. Through an element it changes the element, as `xs[0].append(v)` does. Read as a value, `__setitem__` and `__delitem__` answer `None`, as `append` does, and an in-place name answers its changed receiver, which DKE Python reads only as a statement, so either is a type_error saying so. Beside such a `**` and bound to no name, the change is made before the value is refused, as a change read as a value is (§8.14): `print(xs.__setitem__(0, 5, **opts))` writes `5` and is then refused. A set's in-place name given an operand that is not a set answers `NotImplemented` and changes nothing in Python. Here it is refused, as an operator's special name is above. A class this program declares holds no special name of its own (§13.4). ## 9. Dynamic semantics ### 9.1 Evaluation order Expressions evaluate **strictly, left to right**. The sub-expressions of a binary operator evaluate before the operator; the index expression of `[]` evaluates before the indexing; an argument list evaluates left to right before the call. An argument given by name evaluates where it is written, whichever position it fills (§8.13). A method call's receiver evaluates before its arguments; a slice's start, end and step evaluate in that order; and a dict literal evaluates each key and then its value before the next key. A call of a stored script is ordered as any other expression is: it runs, and anything it prints appears, where this order reaches it. The short-circuit operators `and`, `or` are the only exceptions: the right operand evaluates only if needed. Statements within a suite execute in textual order; an assignment's binding becomes visible immediately after its initializer evaluates. ### 9.2 Control flow #### 9.2.1 `if` `if :` with an optional `else:`. The condition may be any value — it is tested for **truthiness** (§8.2), so `if xs:` / `if s:` / `if n:` branch on non-empty / non-zero exactly as CPython does. #### 9.2.2 `for` `for x in :`. The `` is one of the six finite collections, a constructed `list`/`set`/`dict`, or a `string` (§6.3); `x` is bound to each element in order — for a string, each character (a one-character string); the body executes once per element. A store-derived collection is read once, at entry, so changing the store during the loop does not change the iteration. A list, set or dict the body changes is walked as §8.14 says: a list as it stands at each step, never past the count it had at entry, and a dict or set as it stood at entry, refusing a change of its size. A `for` MAY carry a trailing `else:` clause: ``` for x in : S else: Se ``` `Se` runs once, after the loop finishes, UNLESS the loop was left by a `break` (§9.2.7). An empty `` — zero iterations — still runs `Se`. `Se` executes in the scope enclosing the loop, where the loop variable `x` holds the last element bound (§6.1), or, after an empty ``, whatever it held before the loop. Because `Se` is straight-line, the termination guarantee (§9.5) is unaffected. #### 9.2.3 `match` `match :`. The scrutinee is evaluated once; control transfers to the `case` clause whose label matches the runtime discriminator of the scrutinee value. A `match` with a class set is exhaustive per §6.2, and one and only one clause runs; one without — over a value Python has, or over a `value` naming no class — runs at most one, and none when no pattern matches. When the scrutinee is a `value` (§5.6), the label matched is the runtime **class** of the stored content — one of those listed in §6.2. An arm whose label is an alternation (§6.2) matches when that class is any one of the alternatives. An arm with a sequence or a mapping pattern (§6.2) is taken when the scrutinee has the shape its label writes, and its bindings take the values at those positions. Named arms are tested IN SOURCE ORDER, and the first whose label matches and whose guard (§6.2) holds is the one that runs. A class may appear in more than one label only when every earlier arm naming it is guarded, so among class labels order matters exactly where guards are involved. A **value pattern** (§6.2) is tried where it is written, among class arms too: a literal matches when the scrutinee `==` it, `_` matches anything, and a capture matches anything and assigns it to its name — before the guard is evaluated, keeping the value when the guard is false. If no named arm is taken, the `case default:` arm runs, wherever it is written in a `match` with no value pattern; beside one it is the last arm (§6.2). An arm's `as ` binding, if present, receives the content typed at the arm's class for the duration of the arm — and is in scope in that arm's guard, so a guard may read it. A guarded arm whose guard is false is not taken, and evaluation continues with the next arm. A `match` with a class set cannot fall out of the bottom of the statement: a guarded arm does not satisfy exhaustiveness (§6.2), so a `match` with no `case default:` must name every class in an UNGUARDED arm, and one of those always runs. A `match` without one falls out when no pattern matches, and control continues after it. #### 9.2.4 `try` ``` try: S except refuse as r: Sr except engine_error as e: Se else: So finally: Sf ``` Statements in `S` execute in order. If a statement raises a refuse, its result does NOT bind and control transfers to the **first** `except refuse` handler (if present) with `r` bound to a `refuse_info`. If a statement raises an engine_error, control transfers to the **first** `except engine_error` handler (if present) with `e` bound to an `engine_error_info`. If no handler for the raised category is present, the refuse / engine_error propagates out of the `try` and out of the script. After a handler completes, execution continues with the statement following the `try`. Handlers may appear in **any order** and **any number**. Order does not decide which category a handler catches — that is fixed by the category it names — so a handler is selected by its category and, among handlers naming the same category, by position: the first wins and any later one for that category is **unreachable**. First-wins is CPython's rule for duplicate `except` clauses, and DKE Python follows it. An unreachable handler does not make a program ill-formed: it compiles, and the first handler runs. An implementation SHOULD name each unreachable handler in a note beside the result (§10.6), which is where it goes beyond CPython, whose compiler says nothing about one. The `except` handlers MAY be followed by two optional clauses, in this order: - **`else:`** — permitted only when at least one `except` handler is present. `So` runs iff `S` completed with no refuse or engine_error. `So` is **outside** the protected region: a refuse or engine_error raised in `So` is NOT caught by this `try`'s handlers and propagates. - **`finally:`** — `Sf` runs on **every** exit path from the `try`: after a clean body, after a handler, after `else`, when a refuse or engine_error propagates uncaught, and when a `break` or `continue` in `S` leaves the `try`. `Sf` runs last. If `Sf` itself raises, or issues a `break` / `continue`, that outcome supersedes whatever the `try` was about to do. A `try` MUST have at least one `except` handler or a `finally` clause; a bare `try:` with neither is a compile-time error. The clause order is `try` → `except`* → `else` → `finally`. A `try` with neither, and an `else:` with no `except` before it, are grammar errors (§10.1), as they are in CPython, so a syntax error below them is not the answer over them. Each is answered where a handler was to begin: at the `else`, or at the first token after the clauses, which below a dedent is column 0 of its line (§10.1), and at the end of the line the source ends on. The lines `try:`, ` pass` and `print(1 +)` are a parse_error at line 3, column 1. An `except` refused for its category still counts as a handler, so `except ValueError:` is answered for its category. #### 9.2.5 `branch` block ``` with branch(""): S ``` A `branch` block opens an isolated branch named ``, executes `S`, and on normal completion commits the branch. If any statement in `S` raises an uncaught refuse or engine_error, the implementation MUST roll back the branch and re-raise. A branch's name begins with a letter or `_` and continues with letters, digits, `_` or `-`; it holds no `.`, space or other character, and cannot be empty. A name outside that rule is a `refuse` naming the character at fault, raised before `S` runs. A branch may not take the name of a world that exists (§7.1.2). Leaving `S` early by `return`, `break` or `continue` is a normal completion and **commits** the branch, as reaching the end of `S` does. Only an uncaught refuse or engine_error rolls back. The distinction is between leaving and failing, not between leaving early and leaving at the end: a program that means to abandon the branch's writes says so with `rollback` (§9.2.6). #### 9.2.6 `commit` / `rollback` Both are bare statements. Written inside a `branch` block, they resolve to that block's branch and terminate it. The block's remaining statements still execute, and they execute with **no branch open**: a write among them is an ordinary durable write, outside the resolved branch and unaffected by it, and the implementation MUST report it as it reports any other write. Outside a `branch` block, each is a refuse (no active branch). Nested branching is forbidden, and so is a branch inside a `world` block (§9.2.9). #### 9.2.7 `break` / `continue` Both are bare statements, valid only lexically inside a `for` loop (§9.2.2) — `break` or `continue` outside a loop is a compile-time error (not a runtime refuse). `break` stops the loop immediately; `continue` skips the rest of the current iteration and proceeds to the next element. Both may appear nested inside an `if`, `match`, or `try` block within the loop body, and each resolves to the **innermost** enclosing `for` loop; a `break`/`continue` in an inner loop does not affect an outer loop. Because a `for` iterates a finite collection and these statements only ever **shorten** the iteration (never extend it), the termination guarantee (§9.5) is preserved. Loop context does not cross a function boundary: a `break`/`continue` must be inside a loop in its **own** definition, not merely called from within a loop. #### 9.2.8 `return` A `return` statement ends the enclosing script immediately. It bubbles **past** any enclosing `for`, `match`, `if`, or `try` — a `return` is not a loop signal, so it does not run a `for … else` and is not drained by a loop the way `break` / `continue` are (§9.2.7). One interaction is fixed: a `return` leaving a `try` still runs that `try`'s `finally` clause **first** (§9.2.4), and if the `finally` itself returns, breaks, or continues, that outcome supersedes the pending `return`. - **Bare `return`** (no expression) exits the script with no value. It is valid in any script. Statements after the taken `return` in the same suite do not run. - **`return `** (a value script only, §4.2) evaluates `` and exits, delivering the value. When the script was reached as a **value-call** in a caller (`y = f(args)`, §4.4), the value binds the caller's target; when the script was run at the top level ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)), the value is surfaced on the invocation result. Because a `return` only ever **shortens** execution (it starts no iteration and scripts do not recurse, §9.7), the termination guarantee (§9.5) is preserved. #### 9.2.9 `world` block ``` with world(): S ``` A `world` block runs `S` inside the world named `` (§7.1.2), a `string` expression evaluated once, before `S`. Entering brings the world up to date with the store. `S` reads everything the store holds and everything the world holds, and every write in `S` lands in the world. Leaving `S` by any path leaves the world: reaching its end, `return`, `break`, `continue`, or an uncaught refuse or engine_error. The statements after the block run outside it. Nothing is committed or rolled back on the way out: what `S` wrote before it ended stays in the world, and is merged or discarded only by `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 at run time, through a script called from inside one block that opens the other, each is a `refuse`. `commit` and `rollback` inside a `world` block, outside any branch, are refuses (no active branch), as anywhere else. ### 9.3 Refuses as control flow A refuse is a recoverable runtime outcome of a verb call. Refuses arise from input that the store rejects (out-of-surface slot, incomplete slots, ambiguous match, invalid source slot, write conflict, …). The list of refuse reasons is defined by the target service; DKE Python guarantees only that a refused statement's binding does not produce a value and that control transfers to the enclosing `except refuse` handler if present. A statement that would bind a value and refuses leaves the target identifier **unbound**; any subsequent reference within the same block is a refuse on the same path. ### 9.4 Engine errors An engine_error indicates that the target service could not carry out a well-formed request made from the user's source. Engine_errors are distinct from refuses; they indicate a service-side issue, not a user-side correction opportunity. They are caught by `except engine_error`. ### 9.5 Termination guarantee **DKE Python guarantees bounded termination** (it is bounded-terminating). Every well-typed program terminates on every input. The guarantee follows by structural induction: - The only iteration construct is the `for … in` over a finite collection (§6.3). The collection's length is determined at iterand evaluation; the loop runs at most that many times, whatever its body does to the collection (§8.14). - There is no `while`, no unbounded loop, no goto, no continuation. - `next` (§8.6) reads at most one element per call and is not a loop. Every call a run makes is reached from straight-line code or from a `for` over a finite collection, so a run makes a bounded number of them. - There is no recursion. A stored-script call is inlined at compile time (§9.7); a direct or indirect call cycle between scripts is a compile error. - All other control-flow constructs (`if`, `match`, `try`, `branch`) reduce body size, not increase it. A target service MAY additionally impose execution budgets (statement count, wall-clock time, **the amount of data one run may build**) for operational reasons; such budgets are NOT part of the language specification. Note what termination does and does not promise: it bounds how LONG a program runs, not how much it HOLDS at once, and those are separate properties — a program can be bounded-terminating and still build a collection larger than the service can hold. A budget of this kind refuses the run (§10.3-class, reported as a refuse) rather than failing it, and is not catchable by `try` (§9.3): a handler able to swallow a resource limit could retry inside a bounded loop and defeat it. ### 9.6 Determinism contract Given (i) the same DKE Python source, (ii) the same target store state at invocation start, and (iii) the same argument values, two invocations of one program MUST produce **byte-identical** transcripts. This holds across re-invocation, across implementation process restarts, and across implementation versions that share the same spec version (§11). Determinism applies to the response shape — the response header's call frame (with its success verdict) and the ordered sequence of `print` outputs and store effects the body lists ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)); the exact prose of that human render is presentation, not a versioned contract ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). Determinism does NOT constrain the wire encoding of the request or any internal representation. ### 9.7 No runtime recursion; inline expansion DKE Python has no runtime function-call mechanism. A stored-script call `(args)` (§4.3, §6.8) is **compile-time inline expansion**: each invocation site is replaced by the callee's body with parameter bindings substituted. Consequently callees MUST be compiled before callers, cycles are forbidden (a compile error), and inlining is one-shot (recompiling a callee after the caller does not change the caller's stored body until the caller is also recompiled). A **value-call** in expression position (`y = f(args)`, §4.4) follows the same model: it is not a runtime call either, and a value script that a caller reaches by value-call is subject to the identical compile-before-call, no-cycles rule. Because value delivery is a compile-time construction over the callee's body, a value-call adds no recursion and no runtime call frame; the termination guarantee (§9.5) is unchanged. A callee written below its caller compiles when the caller reaches it. A chain of such calls going more than 1000 levels is a type_error, and the same chain written callee first is not. --- ## 10. Error model DKE Python distinguishes four error categories. Every conforming implementation MUST classify outcomes into one of these: | Category | Stage | Recoverable in source? | Exit code (CLI) | |----------|-------|------------------------|-----------------------------| | `parse_error` | static (lexer / parser) | no | 4 | | `type_error` | static (typechecker) | no | 5 | | `refuse` | runtime (verb call) | yes (`except refuse`) | 1 (uncaught) | | `engine_error` | runtime (verb call) | yes (`except engine_error`) | 2 (uncaught) | Exit code 0 indicates success; exit code 3 indicates a CLI argument error (not a language-level outcome). ### 10.1 `parse_error` The source is not a well-formed token stream or does not match the grammar of §4. Examples: - A tab in indentation, or a dedent that matches no open level (§3.2). - Unterminated string literal. - A closing bracket that closes no bracket, or one of another kind. - A bracket still open at the end of the source. - Codepoint outside XID at identifier position. - A reserved word (§3.7) in user-identifier position. - An empty compile unit — no declarations after whitespace and comments. - A non-exhaustive `match` written with a missing arm (raised as type_error, §10.2) versus a malformed `case` header (parse_error). - A name argument that is not an identifier-shaped bare word (§6.5). A conforming implementation MUST report the line and column of the first offending token. Both count from `1`, and the column counts characters, not bytes, as Python counts the offset of a syntax error, so a line holding `å` or `汉` is placed where a reader counting in an editor lands. The line break that ends a line with a comment begins where the comment begins, as CPython's tokenizer places it, so an error answered at that line break is answered there. The line `x = 1 + # c` is a parse_error at column 10. What is answered at the end of the source is answered where its final line break stands, one past the end of its last line, as CPython answers it, and not at the start of a line after it; a source with no final line break is answered where one would stand. The line `if True:`, ending the source, is a parse_error at column 9. A source whose final line break is a carriage-return / line-feed pair has one empty line more in CPython 3.11's reading, so what is answered at its end is answered on that line: `if True:`, ending in the pair, is a parse_error at line 2, column 1. Answers CPython gives at a token with no column of its own count the column from zero instead, as CPython counts it. They are an unexpected indent (§3.2); a grammar error at a dedent a token follows, such as a block missing below a header whose next line returns to a shallower level, answered at that token; and the end of the source below a line that ends in no header's `:`, as below a decorator, which is answered at column 0 of the line the source ends on. The three lines `def f():`, ` if x:` and `print(1)` are a parse_error at line 3, column 0. The line `@f`, ending the source, is a parse_error at line 1, column 0. It is still met at the end of the source, after every form above it that is answered in source order. **A grammar error comes first.** CPython reads a whole program before it runs any of it, so a `SyntaxError` anywhere is the answer, whatever else is wrong above it. A DKE Python parser also decides things that are not grammar — whether a qualified name is a class, what a type annotation names, the shape of `isinstance`'s second argument, whether a form CPython writes is one this language admits — and refuses at the first of them. When the program also breaks the grammar on a later line, a conforming implementation MUST report that grammar error in place of the refusal above it. The three lines `x = 1`, `print(isinstance(x, 5))` and `y = 1 +` are a parse_error at line 3, "expected an expression", and not at line 2. A **grammar error** is a place where the grammar requires a token or a word and the source holds something else: a missing `)`, `:` or line break, or an expression that is not there. A word CPython reserves too, written where a name or a value is read, is one as well: this language teaches it as a reserved word (§3.7), and CPython's parser refuses it there. It is answered where CPython answers it, at the word, or, at the start of a statement, at the token after a word that begins a statement or an expression, as `return` does in `return = 1`. One after a `.` is answered at the word, as `if` is in `xs.if` (§3.7). A word only this language reserves, such as `remember`, is a name to CPython, and refusing it stays a decision. The two lines `y = def` and `z = 1 +` are a parse_error at line 1. Two values side by side inside a bracket, with nothing between them, are a grammar error too, answered as CPython answers a forgotten comma: where the first value begins, past parentheses that only group it, so `[(a) b]` is answered at the `a`. CPython answers some such pairs where the second begins, and so does a conforming implementation: a pair whose first value follows a conditional's `if` or a `for`, begins with `match`, `case` or `_` or with a name a string follows, is `print` alone, or is a name an `import` lists. A value after a conditional's `else` is a first value like any other, so `x = [1 if b else 2 3]` is a parse_error at column 18. A name that begins the spelling of `case` or `match`, such as `c` or `ma`, counts as one of those soft keywords, since CPython 3.11.2 compares a name with a soft keyword over the name's own length. After such a name, or a name a string follows, CPython reads on from the next token unless it is a `(`, and a pair it finds there is answered at its first value: `x = [m.x 2]` is a parse_error at column 10, at the `2`, and `x = [c[0] 2]` at column 7, at the `[`. Where nothing it reads there has a second value after it, no value after the first is read on, even into a bracket the source never closes: `x = [c < b d` is a parse_error at column 12, at the `d`, as `x = c < b d (` is at column 11. A pair it does find is read as any other: where its second value runs into a bracket the source never closes, the bracket is the answer, as `x = [c d e` is at column 5. A name begins that reading after a keyword Python reads an expression after, such as a statement's `if`, `while` or `del` or a conditional's `else`, and after a `match` that begins a statement: `if c d e (` is a parse_error at column 10, at the `(`. No value is read on after a comprehension's iterable or one of its conditions, which Python reads as one operand with no rule for a forgotten comma: `x = [e for q in xs if e d` is a parse_error at column 25, at the `d`, whether the bracket closes or not. A `for` statement's iterable begins after its `in` and a `raise`'s cause after its `from`, and a name there begins a reading as one after `if` does: `for q in c.x d (` is a parse_error at column 14, at the `d`, and `raise e from e d e (` at column 20, at the `(`. A `raise` of an exception that is not built in is read as Python reads it before it is refused, so `raise e d` is a parse_error at column 9. A starred element of a statement is one value that only an operator that computes continues, so a comparison, a boolean operator or a conditional's `if` after it is answered where it stands: `*e < b d` is a parse_error at column 4, at the `<`, and `x, *e < b = 1` at column 7. A line that begins with a list, a set or a dict the source never closes, or one a bracket never closed follows, is read as Python reads it, so a grammar error inside it is the answer: `[q for q in *a b (` is a parse_error at column 13, at the `*`. An `=` inside a bracket never closed, with nothing after the value it would assign, is answered as the bracket, since Python reads past that value to the end: `x = [a, b = c` is a parse_error at column 5. A line that begins with a `*` and assigns nothing, as `*e, a` does, is an expression standing alone, refused at its start as `a, *e` is, and read as Python reads it: `*e, a if b` is a parse_error at column 5, at the `if`. A starred element annotated alone is neither a tuple nor an expression, and is answered at the `:`, so `*e: int` is a parse_error at column 3; a tuple annotated is answered at its start, a trailing comma's included, as `a,: int` is at column 1. Python reads an annotation after a `:` that follows an expression before it weighs the target, so where none reads there the `:` is answered, as `f(a):` is a parse_error at column 5, and where one reads the statement is answered at its start, as `f(a): int` is at column 1. A `print(…)` the line goes on past keeps no value, as a script's call does, and what follows it is read as Python reads a trailer: `print (a)[, 1` is a parse_error at column 11, and `print (a)[0]` is an expression statement, which runs. So is a script's call the line goes on past: `f(a)(, 1` is a parse_error at column 6. A dict's `**` is read as a list's `*` is, so a grammar error after its operand is answered before the unpacking is refused: `x = {**e < b}` is a parse_error at column 10, at the `<`. After a line's first element and a comma, Python reads every value on, side by side or not, so a bracket never closed after them is the answer whatever stands before them: `y, (a) b c (` is a parse_error at column 12. A call written without its parentheses among them is answered at its word, unless its arguments are read on to that bracket: `y, print 's' b 't' (` is a parse_error at column 4. Inside a rule (§14), a form only the rule's grammar reads, such as `known_absent(…)`, keeps its own answer. The line `x = [1, 2 3]` is a parse_error at column 9. Where a reading stops at a token CPython reads on from, the grammar error is answered where CPython stops. A `.` after a value with no name after it is answered at the token after the `.`, so `x = a.-b` is a parse_error at column 7, and a `not` after a value with no `in` after it at the token after the `not`, where `not in` would go on. Inside a bracket, a `not` that begins a second value is a forgotten comma, as above. A `=` inside a call's parentheses, after an argument that is no name, is answered where the argument begins, past parentheses that only group the whole of it, as CPython answers an assignment to an expression: `f(a.b=1)` is a parse_error at column 3. `True`, `False` and `None` are no names, so `f(True=1)` is answered at the word. An argument that begins with a name and its `=` is a keyword argument, so a second `=` after its value is a grammar error where it stands: `f(a=b=1)` is a parse_error at column 6. Where CPython reads a named expression — in any other bracket, and after an `if`, an `elif`, a `match` or the `if` of a `case`'s guard — a `=` is answered the same way, where what it would assign to begins, when operands joined by arithmetic stand on each side of it and no `=` follows the second: `if x = 1:` is a parse_error at column 4, and `x = [1, (a)=1]` at column 10. A `while`'s condition is one too, read as Python reads it past the refusal of the `while`, so `while a = 1: pass` is a parse_error at column 7. Each operand is read as Python's parser reads it, so one whose bracket does not parse inside matches no such rule, and the `=` is answered where it stands: `if a = (b +): pass` is a parse_error at column 6. Python reads that operand with its rules for an invalid one, so a grammar error those rules raise inside it is the answer, before the `=` is weighed: `if a = {b:}: pass` is a parse_error at column 10. An operand whose trailer the source never closes is read to the primary before the trailer, where Python's reading of the trailer stops on the bracket's line, so `if a = b(: pass` is a parse_error at column 4; where that reading reaches the end of the source or a later line, the bracket is answered, as `if a = b(` is at column 9. What it would assign to is named as CPython names it unless it is a name alone, which is asked whether `==` was meant, so `x = [1, (a)=1]` is told "cannot assign to name here", and parentheses that only group it are read through. What begins with a list, a tuple, `True`, `False` or `None` before the `=`, and a `=` in a dict's value, a slice, a comprehension's clause, a lambda's body or a conditional's condition, are answered at the `=`, as CPython answers them. An `await` with a value after it, which Python reads as its keyword, is a value like any other among a statement's targets, and is named as one: `a, await b = 1` is a parse_error at column 4, "cannot assign to await expression here", and `a, await b += 1` one at column 1, where Python names the tuple. A value alone in parentheses after a `*` among them is a grammar error at the star, raised as Python reads the element and before any `=` is weighed: `f(a), (*b) = 1` is a parse_error at column 8, "cannot use starred expression here". Where CPython reads a named expression (§8.19) its value is read on, so a grammar error in it is reached: `y = (a := 1 +)` is a parse_error at column 14. Where CPython reads none, as in `y = a := 1`, the `:=` is a grammar error where it stands. Where CPython reads a named expression, or the expressions a statement begins with, a target that is no name is a grammar error where it begins once a value follows the `:=`, named as CPython names it: `y = (a.b := 1)` is a parse_error at column 6, "cannot use assignment expressions with attribute", and parentheses that only group the target are read through, so `y = ((a) := 1)` is one at column 7. Where Python's reading of the value after the `:=` reaches the end of the source inside a bracket never closed, the bracket is the answer, the one last opened, as `y = (a.b := 1` is at column 5 and `y = (a.b := (1` at column 13, inside the value, and so is a call's: `(a.b := f(1` is a parse_error at column 10. A bracket in the value that the source closes ends that reading at its close, so `y = (a.b := f(1 +)` keeps the target's lesson at column 6, and a call the source never closes whose reading stops on its line is backed off to the value before it, as Python backs off a trailer that fails: `(a.b := f(1: pass` is a parse_error at column 2, "cannot use assignment expressions with attribute". A named expression that begins a `{…}` makes it a set, so a `:` after its value is a grammar error where it stands: `y = {a := 1: 2}` is a parse_error at column 12. After a dict's first entry, a key with no `:` after it is answered as CPython answers it: on the line the key begins, at the column of the key's last character, inside parentheses that only group the whole key, so `x = {1: 2, a.bc}` is a parse_error at column 15. The key is the longest expression CPython parses there, so one whose first operand does not parse, as `(3 +)`, is answered at its grammar error: `x = {1: 2, (3 +)}` is a parse_error at column 16. A group holding an `=` reads as no key, and Python's reading of it as a named expression answers the `=` where what it would assign to begins: `x = {b: c, (c=d)}` is a parse_error at column 13. A call whose arguments stand in an order CPython's grammar does not read, a positional one after a keyword one, is read only to its name, so `x = {1: 2, f(c=d, g)}` is a parse_error at column 12. A `:` after a key with no value after it is answered at the `:`. After `->`, CPython reads the longest expression it can and requires the `:` there, so a return annotation that ends before its `:` is answered where it ends, and an empty one at the `->`: `def f() -> int + -:` is a parse_error at column 16, "expected `:`". A collection type written with angle brackets, such as `list`, is this language's own, and is read as one. Each part between the `=`s of a chained assignment (§8.12) is read as CPython reads an expression list and answered where that reading stops, so `x = a b = 2` is a parse_error at column 7. In a `case`, a `*` takes a name to capture or `_`, and is answered at what follows it when that is neither; a starred pattern written alone, as in `case *rest:`, is answered at the token after its name, since a sequence pattern is written in brackets or with a comma (§6.2): `case [*rest]:` or `case *rest,:`. A program with no grammar error is refused at its first refusal. A grammar error below a refusal inside a `@rule` or `@constraint` is reached, and so is one later on the line of a refusal inside an operand: a name, a literal, a call or a bracketed group, with what indexes it, calls it or reads a field of it. A rule's lines are read by the rule's own grammar (§14), and where it stops in a conclusion's value, in an `if` condition or after a constraint's `violated`, the value or the condition is read again from where it begins, as Python reads an expression. A grammar error that reading meets past where the rule's grammar stopped is the answer, so a conclusion `k.g = 1 +` is answered at the column past its `+`, and `if k.a > 1 +:` at its `:`, as Python answers them. So is one in a condition holding a premise refused as a decision, such as `if k.a is:`. Where Python's grammar holds the value or the condition, as in `k.g = 1 + 2`, the rule's grammar still refuses it where it stopped, and that is a grammar error there, as it is where the rule's grammar requires a field or a line of the rule's shape and the line holds something else. A grammar error later on the refusal's line is reached when the refusal stands in a header too: in a parameter or its default, a return annotation, a base class, the type of a field or of a binding, the exception an `except` names or the class a `case` names, and at a name a definition already holds. In a parameter list Python reads, a `**` parameter, a parameter after a starred one and a word this language reserves (§3.7) are read past as Python reads them, and in a method so are a special method's name and a first parameter other than `self`. A grammar error is reached in a decorator refused at its name or in its arguments, which is read as Python reads a decorator, and in or after a `lambda`, which is read as Python reads one. On a line of a rule or a constraint, a refusal is stepped past where it stands, and the rule's grammar reads the rest of the line. A `*` or `**` this language refuses where an argument or an entry of a dict begins is read past as Python reads it. A `*` before an element of a list, a tuple or a set, bracketed or not, spreads a collection and is refused nowhere (§8.15). It is read as Python reads it all the same, so what this paragraph says of a refused star's operand and of the grammar error after it holds of a spread's too. A grammar error later in its bracket, later on its line or below it is then the answer. One with no value after it is that grammar error. One whose value stands alone in parentheses is a grammar error at the star, as in Python, ahead of any grammar error after it: `y = (*a)` is a parse_error at column 6, "cannot use starred expression here", and so is `del (*a)`. Two values side by side after an argument's `*` or `**`, or after the `*` of a bracket's first element, are answered at the first, past its `*`, where Python reads a whole expression; after a dict's `**` or a later element's `*`, they are answered at the second. The line `f(*a, 1 +)` is a parse_error at column 10, "expected an expression". A `**` that begins a dict, with an operand and a comprehension's clauses after it to the `}`, is a grammar error at the `**` in Python's words, "dict unpacking cannot be used in dict comprehension", and a `*` before a dict's value is one at the `*`, "cannot use a starred expression in a dictionary value": `y = {**a for a in b}` is a parse_error at column 6, and `y = {a: *b}` at column 9. With no operand Python reads after the `*`, the grammar error is where its reading of one stops: `y = {a: *}` is a parse_error at column 10. The same holds where an element of a list, a tuple or a set begins, which Python reads a `bitwise_or` after: a keyword after the `*` begins none, and a comparison, `and`, `or` or a conditional's `if` after its operand cannot go on, so `y = [*not a]` is a parse_error at column 7 and `y = [*a < b]` one at column 9. Just inside a bracket Python reads the `*` and an expression first, and answers that expression as it answers any other: where the reading runs into a bracket the source never closes, the bracket is the answer; a conditional with no `else` is a parse_error where it begins, as `y = [*a if b]` is at column 7; and a value straight after it is a forgotten comma where it begins, as `y = [*a < b c]` is at column 7. The same keyword is a parse_error at the word after the `*` or `**` that begins a subscript's index or a call's argument, which Python reads an expression after, as `y = f(*yield)` is at column 8, unless another argument comes before a `*`, which Python answers where the arguments begin; and Python reads no slice after a starred index, so `y = a[*b:1]` is a parse_error at the `:`, column 9. Elsewhere in a list, a tuple or a set, and after a dict's `**`, Python reads nothing on from a `*`'s operand: a value or a `not` straight after it is a parse_error where it stands, as `y = [1, *a b` is at column 12, and not the bracket the source never closes. A statement that begins with an element and a comma reads on all the same, so `y, *a b (` is a parse_error at column 9, the `(`. Nor does Python read a `not` with no `in` after the operand of a bracket's first `*`, or of a `return`'s or a `yield`'s `*`, as a comparison: `y = [*a not ]` is a parse_error at column 9, at the `not`. Its rule for a forgotten comma still reads a second value after the first, as above, and reads on into a bracket the source never closes where the source ends after the `not`. A prefix `+`, which this language refuses (§8.3), is read past as the sign Python reads: `y = +a b` is a parse_error at the `b`. A verb call this language refuses at an argument, for a keyword argument the verb does not take (§7.1) or for a second argument where it takes one operand, is read on to its `)` as Python reads a call's arguments, so a grammar error later in the call is the answer: `pin(K.s.a, b, 1 +)` is a parse_error at column 18. So is a verb call refused at a keyword argument it takes, written out of its place (§7.1). So is a call of a builtin, an aggregate query (§7.1.1) or a method refused at a keyword argument it does not take, or at an argument it does not take after its keyword arguments: `y = len(x, a=1 +)` is a parse_error at column 17. A verb call or a call of a builtin refused at one of its arguments for any other reason is read on from the argument after it, so a grammar error there is the answer: `y = isinstance(1, 5, 1 +)` is a parse_error at column 25. A grammar error inside the refused argument itself is not reached. A call refused at an argument more than it takes (§7.1) is read on from that argument, so a grammar error in it or after it is the answer: `y = len(x, c, 1 +)` is a parse_error at column 18. So is a verb call refused at a path argument that goes on as an expression (§7.1): `y = current(K.s.a +, t)` is a parse_error at column 20. So is one refused at a path argument that is no path, and a hypothesis (§15) refused at either: `y = current("s", 1 +)` is a parse_error at column 21. A call refused as short of an argument it takes (§7.1) is read past its `)`, so a grammar error below it is the answer: the lines `remember(K.s.a)` and `z = 1 +` are a parse_error at line 2. A `*` or `**` argument after a verb's or a builtin's keyword arguments is refused as one is anywhere in a call. An argument out of order in a call (§7), a verb's or a builtin's among them, is a grammar error, as it is in Python. A `*` argument after a `**` one is answered where the call's first argument begins. So is a `*` that no expression follows, after another argument: `f(1, *)` is a parse_error at column 3. A positional argument after a keyword or a `**` argument is answered once the rest of the call is read as Python reads it: at the `)`, or where that reading stops. Two values side by side, or an argument that assigns to an expression, in that rest are answered first, at their own place. `f(a=1, b, c=2)` is a parse_error at column 14, the `)`. So is a call that stands among an assignment's or a `del`'s targets, which Python's rule for its arguments raises in before any target is named: `x, f(c=d, g) = 1` is a parse_error at column 12, and `del f(c=d, g)` one at column 13. Such a call side by side after another value among a statement's elements is no call to Python, which reads its bracket as a tuple of named expressions and answers the first that assigns, at its name: `y, foo b(c=d, g)` is a parse_error at column 10, where `y, foo b(c=d)`, a call Python reads, is one at column 8, at the `b`. A class's bases are a call's arguments to Python and are read as one, so an argument out of order among them is answered as it is in a call: `class C(B, x=1, 2):` is a parse_error at column 18. A base that is not a class's name, which this language refuses (§13.8), is read past as Python reads the argument it is, so a grammar error after it is reached. A call refused at one of its arguments is read to its `)`, so the brackets after it are reached. What follows a refused value is read as Python reads it, each call's arguments and each subscript's parts, so a grammar error in the brackets after it is reached: `x = datetime("bad")[1 +]` is a parse_error at column 24. So are the arguments of a method call refused at its name, whether it is a method a built-in value or a module does not have or one a class does not declare: `y = s.foo(1 +)` is a parse_error at column 14. A statement refused at its first word is read as Python reads it, its body included, so a grammar error in it is reached: `while True: x +` is a parse_error at column 16. A `while` and an `elif` are reserved (§3.7), as they are in Python, so a line one begins is the header Python reads, before a `(` too, and is refused at its first word as one whose condition opens with no bracket is: `while (a): pass` is a parse_error at column 1, and so are `elif (a) = 1: pass` and `elif (a: pass`, whose bracket is never closed, with no `if` above them. Where Python's reading of the header raises first, that is the answer: in `while (a) = b(: pass` it raises at column 8, "cannot assign to name here", and `while.x.y = 1` is a parse_error at column 6, where no condition begins. An `elif` with no `if` above it is a grammar error at its word, as in Python, ahead of anything in its condition or its body and of a grammar error below it: `elif a = b(: pass` is a parse_error at column 1. A string not terminated below it, or a number Python's tokenizer refuses, is the answer instead, at its own line; a bracket never closed below it is not. So is a `class` inside a block or a class body, which is declared only at the top level (§13.1), with its bases and its body: `class D:` inside an `if`, with `x: int = 1 +` in its body, is a parse_error at line 3, column 21. What a decorator refused at its name decorates is read as Python reads it, so a grammar error in it is reached: `@nosuch` above `def f():` and `y = 1 +` in its body is a parse_error at line 3, column 12, and what follows a decorator must be a `def` or a `class`. A definition that reads as a rule or a constraint keeps the refusal, since its lines may be a rule's grammar under a misspelled `@rule` or `@constraint`. A word only this language reserves, where Python reads a name, is read as that name past its refusal, so a grammar error after it is reached: `remember = 1 +` is a parse_error at column 15, and so is one in a definition, a class or a method that word names, after an `except` that binds it, or in a target list or a field that holds it. A `True`, `False` or `None` before a comma at a statement's head begins the expressions Python reads the statement as before it weighs any target, and Python reads it there as the value it is, so a grammar error later on the line is reached: `True, b +` is a parse_error at column 10, and `True, lambda x=1, y: 0 = 1` one at column 19. A parameter list or a `lambda`'s parameters that Python would not read are answered at the place Python names, ahead of a refusal in or around them and of this language's own grammar past that place. Python names it at a parameter after a `**` one, a second `/` or `*`, a `/` after a `*` or before any parameter, a `*` right after a `/` with no comma between, a default on a starred parameter, a parameter with no default after one with a default and before any `*`, a bare `*` that no named parameter can follow, and a parameter written in brackets. The line `def f(**kw, y: int = 1 +):` is a parse_error at column 13, ahead of the refusal of `**kw`. A grammar error in a default before that place comes first. Where Python names no place in such a list, it is answered at the first token Python cannot read, as `def f(a, 1):` is at column 10. An import refused past its first word — at a dotted module name, a `*`, the dots of a relative `from`, or a name an earlier import bound to something else — is read on as Python reads an import, so a grammar error later on its line is reached: `import m.n o` is a parse_error at column 12. **The lexer refuses two kinds of form, and only one of them stops the reading.** The first kind is a form CPython reads as a token and this language does not admit: - a bytes literal; - an integer too large for `int`, or a real too large for `real`; - an escape CPython reads and a string here refuses: one that writes NUL, such as `\0`, or a surrogate; - indentation by tabs. A conforming implementation reads past such a form as CPython reads its token, and reports a grammar error anywhere in the program, above the form, below it or later on its own line, in place of the refusal. The three lines `x = 1e999`, `y = 1 +` and `z = 1` are a parse_error at line 2, "expected an expression". A `~` is a sign, which stands only before a value and never between two. `x = 1 ~ 2` is a parse_error at the `~`, and `x = [1 ~2]` is one at its first value, two values with no comma between them. A `@` after anything on its line reads as a `%` does, an operator of its level that stands only between two values, so `x = @1` is a parse_error at the `@`, which no value stands before. The second kind is a token the lexer cannot read at all, and CPython draws a line through it by kind, which a conforming implementation draws too. What CPython's tokenizer refuses is reported before any grammar error in the program, wherever the two stand: an unterminated string, a malformed number, a character outside printable ASCII that no token begins with, a full-width form of an operator included, and a closing bracket that closes no bracket or one of another kind. That holds below a form of the first kind too. The three lines `x = 'a'`, `y = )` and `z = 1` are a parse_error at line 2, "unmatched `)`". A malformed escape, and a printable character no token begins with, such as `$`, `?`, a backquote or a `!` that does not begin `!=`, are refused where CPython's parser meets them. So is what an f-string is refused for, unless the string has no end: CPython reads an f-string as one token, and its parser reads the interpolations. That covers a `"` in an interpolation, an empty one, a single `}`, and whatever an interpolation holds that is refused, a closing bracket included. A conforming implementation answers such a form in source order with grammar errors. A grammar error above it is the answer, and so is one earlier on its own line. Otherwise the form is the answer, over a grammar error below it and over any form of the first kind, unless a token the previous paragraph names stands below it. A `\` that does not end its line is answered in the same order, but nothing below it is read, so not even such a token is reached. A dedent that matches no open level of indentation is answered in the same order, and nothing below it is read either. The two lines `y = 1 +` and `z = 1 $ 2` are a parse_error at line 1, "expected an expression". A malformed escape and what an f-string is refused for are answered where CPython answers them: at the token after the strings written together with that string, which, at the end of a line, is where the line's comment begins or else where the line ends. A dedent that matches no open level is answered one past the end of its line, and where that line ends in a `\`, one past the end of the line it continues to: the four lines `def f():`, ` if x:`, ` \` and `print(1)` are a parse_error at line 4, column 9. Both are still ordered by where they begin. The line `z = "\x4" + 1` is a parse_error at column 11. The three lines `if True:`, ` x = 1` and ` y = 2` are a parse_error at line 3, column 10. A grammar error inside the expression an interpolation holds, and a token there the lexer cannot read, are answered where CPython's own parse of the expression answers them. CPython parses the expression alone, inside parentheses, so the interpolation's `{` is column 1 and each byte after it one column more. The line `y = f"x{g(1 +)}"` is a parse_error at column 7, "expected an expression", and the line `y = f"{é +}"` one at column 6, since `é` is two bytes. A closing bracket that closes no bracket or one of another kind, and a `!` that begins no conversion, are refused as CPython reads the string, and are answered past it as above: the line `y = f"{x)}"` is a parse_error at column 12. CPython parses an f-string's interpolations in turn, as its parser builds the string. So what an interpolation is refused for is ordered where that interpolation begins, at its `{`, and not where the string begins, and a grammar error in an earlier interpolation of the same string is the answer over it: the line `y = f"{1 +}{0x}"` is a parse_error at column 5. A `!` that begins no conversion is refused once the expression before it has parsed, so a grammar error in that expression is the answer: the line `y = f"{1 +!z}"` is a parse_error at column 5 too. A `#` in an interpolation's expression is refused and answered past the string: the line `y = f"{x # c}"` is a parse_error at column 15. A `;` there is a grammar error at the `;`: the line `y = f"{x;}"` is a parse_error at column 3. A `'` or a `!a` in an interpolation is read as CPython reads it there, so a grammar error after it in the same interpolation is the answer: the line `y = f"{d['k'] +}"` is a parse_error at column 10. CPython decodes an f-string's text as it reaches it as well, so a malformed escape after an interpolation gives way to a grammar error in it, and an escape this language refuses where CPython reads one, such as `\0`, is read past as CPython reads it there: the lines `y = f"{1 +}\x4"` and `y = f"{1 +}\0"` are each a parse_error at column 5. A `\` the source ends after (§3.2) is answered where CPython's parser meets the end: on reading past the last token. A grammar error is the answer over it if the parse raises it before reading that far. CPython reads one token past a value where it stops, and stops at a keyword that begins no value, a `:` or a `->` without reading past it. So a grammar error at a token before the last is the answer, and so is one at such a keyword, `:` or `->` standing last; one at a value standing last, or past the last token, is not. A malformed escape or an f-string refusal that would be answered past the `\` gives way to it, since CPython meets the end before it builds that string. The line `x = 1 2 \`, ending the source, is a parse_error at column 10, and the line `y = def \` at column 5. A bracket still open at the end of the source is answered as never closed, at the innermost bracket open there. CPython reads the rest of a source after its parser fails, so a conforming implementation answers the bracket in place of a grammar error or a form answered in source order that stands on a later line than the bracket or at the end of the source. A grammar error or a form answered in source order on the bracket's own line, short of the end, is the answer, and so is one above the bracket: on that line CPython's answer turns on how far its error rules look ahead, which this specification follows only for two values side by side, below. A refusal of a form CPython reads gives way to the bracket wherever it stands, above the bracket, earlier on its line or below it, unless a grammar error the reading finds is the answer: CPython reads on past such a form and still ends inside the bracket. A `\` that does not end its line ends the reading before the end is reached, so it is the answer, and a token CPython's tokenizer refuses below the bracket is the answer too. Two values side by side are read on as CPython reads them: its rule for a forgotten comma reads the whole of the second value, and outside a bracket its rule for a Python 2 statement reads on after a name that begins an expression, through a list of values and the value after each. Where that reading runs to the end of the source inside a bracket never closed, one open around the values or one the reading opens, or stops on a later line than that bracket, the bracket is the answer: `x = [1 2`, `x = [1 2 + 3`, `x = a b (` and `x = a b c (` are answered as never closed, while in `x = [1 2 3` the reading of `2` stops at the `3`, and the two values are the answer. Nothing is read on after a name an import, a `global` or a `nonlocal` lists, a name a `def` or a `class` gives, a parameter's name or a `case`'s pattern, which CPython reads as no expression. A word Python begins a statement with and reserves, which this language reads as a name (§3.7), ends Python's reading of an expression: where one stands after a bracket never closed on the bracket's line and other than the `import` of a `from`, it is the answer, naming the bracket and the word, as `x = [pass` is a parse_error at column 6. A header's body is read as the statement it is, so the word that begins it stands before any bracket in it, and a word after a bracket never closed in the body is the answer as any other: `if a: x = [pass` is a parse_error at column 12. Where the answer stands at the word itself it keeps its own words, as `x = [b for pass` does at column 12, and a word on a later line than the bracket leaves the bracket the answer. Nor after a value that begins with a word CPython takes for a soft keyword: `match.x d (` is a parse_error at column 9, where `match x d (`, with a value straight after the word, is one at column 11, never closed. The two lines `z = (1,` and `2 3` are a parse_error at line 1, "`(` was never closed". The three lines `y = 5(1)`, `z = [1,` and `return 2` are a parse_error at line 2, "`[` was never closed", and the two lines `y = 5(1) + [1,` and `return 2` are a parse_error at line 1, "`[` was never closed". **A keyword argument written twice.** A call that names one keyword argument twice is refused where the name is written the second time, in CPython's words: for `f(a=1, a=2)` the message reads "keyword argument repeated: a". Of the names one call writes twice, the one written first is answered, so the line `y = f(a=1, b=1, b=2, a=2)` is a parse_error at column 22, at its second `a`. CPython finds a name written twice only once its parser has read the whole program, so a grammar error anywhere is answered before it: the two lines `y = f(a=1, a=2)` and `z = 1 +` are a parse_error at line 2. CPython checks a call before anything inside it, its arguments and the expression it calls, so of two calls that each write a name twice, the one holding the other is answered: the line `y = f(g(a=1, a=2), b=1, b=2)` is a parse_error at column 25, and the line `y = h(b=1, b=2).m(a=1, a=2)` at column 24. Otherwise the call that begins first is answered. A built-in function or a verb that refuses a name given twice in words of its own, as `current` refuses a second `known_at=`, refuses it at the same place. A call in an f-string's interpolation is answered where the name is written in the line, and not at a column of the interpolation: the line `y = f"x{g(a=1, a=2)}"` is a parse_error at column 16. **What Python's compiler refuses.** CPython compiles a program only once its grammar has read it, and its compiler refuses programs of its own: 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, `__debug__` bound, more than twenty blocks nested, a keyword argument written twice, and others. Each is a parse_error here, at the place CPython answers, and in CPython's words unless a lesson of this language stands at that same place. A grammar error anywhere comes first: the two lines `break` and `z = 1 +` are a parse_error at line 2. Every other refusal comes after: the two lines `y = 5(1)` and `break` are a parse_error at line 2. Of two refusals CPython's compiler would both make, the one it reaches first is answered, and that need not be the one written first. It reads the names of every scope before it reads a statement, so the three lines `break`, `def f(a, a):` and ` pass` are a parse_error at line 2, column 10. It reads an assignment's value before its targets, so the line `f(await a).b = yield` is a parse_error at column 16. A position it reports counts the line's bytes, where the grammar's counts its characters, and it does so on the English surface alone, since no other surface has a CPython to answer as: the line `def g(name, name):` is a parse_error at column 15. **Where this reading stops short of Python's.** Past a nesting of 100, or 64 steps for a token, it reads only the calls, in the order they are written. A `return` in an `except*` handler is answered at the statement or at its value by what this language folds, which is not all Python folds: `return 2 >> 1` at the `return`, where Python answers at the value, and `return 1 / 0` at the value, where Python answers at the `return`. A `break` in the `try` of a `try` and `finally` inside an `except*` handler is answered where it is written, where Python has lost its place and answers at line -1. `1` beside `1.0` in a mapping pattern is answered at Python's place in this language's words. `from __future__ import annotations as a` binds nothing, since no value here is a feature, and a parameter's `xe` is this language's type syntax, so `def f(a: xe):` is told of the type at column 10. **How long and how deep.** A chain of `and`, `or` or comparisons, the `if`s and `for`s of a comprehension, the alternatives of a pattern and the elements of a sequence pattern read at any length, as in Python. Up to 2,996 `elif`s may lead to an `if`, along its own chain and any chain whose leg holds it, where Python's compiler raises `RecursionError` at the top of a module, and the next is a parse_error where it is written; Python's count falls by one for each block the `if` stands in, and this one does not. An expression nested past 500 levels any other way, by a prefix operator, a chain of `+`, of subscripts or of method calls, or a conditional expression in the `else` of another, is a parse_error at the level past it or, for a chain read whole, at its end, where Python reads about 2,990 levels, and 1,495 method calls. An or-pattern whose alternatives bind a name costs a level an alternative, so it stops at the same count, where Python has none. A chain of more than 32 operands is grouped evenly and not to the left, which gives the value Python gives, but it tells of another pair where its values have no type to join: of four operands written `1`, `1`, `[1]` and `"a"` and then `1` to make 32, the `int` and the `list` at the second `and`, and to make 33, the `list` and the `string` at the third. Reading a program has a limit in work, counted and so the same on every host, which grows with the program's length. Past it, a program Python refuses gets Python's own answer, in place of any lesson of this language, and any other program is a parse_error at line 0, where Python raises `MemoryError` if its parser's stack is what ran out. Nothing written by hand comes near it: among what reaches it are 70 dicts unpacked one inside another beside a syntax error, and a chain of tens of thousands of `lambda`s. ### 10.2 `type_error` The source parses but violates a rule in §5 or §6. Examples: - Use of an unbound identifier — including the kind position of a verb path (§6.7). - A `match` whose case set does not cover the scrutinee's discriminator set, contains a case not in the set, or repeats a case (§6.2). - A `for … in` over a non-collection (§6.3). - Binding a statement-only verb by assignment (§6.4). - `.` for a non-existent field, and `.(…)` for a method the class does not declare. - An operand-type mismatch outside the §8.4 string-concat overload — including a chained comparison (§4.6). - A non-`string` source argument (§7.3). A conforming implementation MUST report the line and column of the offending construct, counted as §10.1 counts them. ### 10.3 `refuse` A runtime outcome of a verb call (or built-in operation) that the target service rejects with a teaching message. Examples: - `remember(K.s.a, v, src)` where the cell already carries a different value under the same source. - `remember(K.s.a, v, src, qualifiers={…})` where the cell already carries the same value under the same source with different qualifiers (§7.1). - `remember(K.s.a, v, src, from, to)` where the cell already carries the same value under the same source over a different window that overlaps this one (§7). - A second `remember` or `update` of one cell from one source, over an overlapping window, at a module's top level ([Python Reference §9.5](/ref/dke/python/#re-submitting-reconciles)). - A `qualifiers=` dict holding a name that is not a name, or more qualifiers than a claim carries (§7.1). - A read naming a kind that is not **known**: `current`, `get`, `subjects`, `list_subjects`, `list_attributes`, an aggregate (§7.1.1), or the field a hypothesis reads (§15) when none of its suppositions writes that kind. A kind is known while the store holds something under it, or while a class declares it (§13.1), in a module as anywhere else. A read of a known kind at a location that holds nothing does not refuse: it answers `empty`, or a collection with no elements. Writing is how a kind comes to exist, so a write never refuses for this reason. A removal is real (§7.1), so a kind whose last claim is forgotten is not known again until something is written under it; declare its class to read it while it holds nothing. - A single-claim read at a location holding competing values (§7.6): `current`, or the field `suppose` or `suppose_absent` reads (§15). - A write — `remember`, `update`, or a supposition — at a computed field (§13.7) or at a field a standing rule concludes (§14.3), unless that rule yields to a recorded value. - A verb path whose segment count does not match the verb's slot shape (§7.2). - A `percentile` rank held by a name whose value is outside `0`–`100` (§7.1.1). - Index out of bounds on `claim_list[i]`. - Division by zero; integer overflow. - `commit` outside a branch. - `forget(K.s.a)` where the address, or something under the named subject, is pinned. Lift the pin with `unpin` first. - `forget(c)`, `would_forget(c)`, `dependents(c)` or `premises(c)` naming a claim the store no longer holds, or a `claim_id` two claims share (§7.1). - `pin`, `unpin`, `prefer` or `unprefer` naming something the store does not hold, or asking for a state that already holds (§7.5). - A read given `known_at=` whose answer rests on a rule changed after that moment or one whose text at that moment the store holds no record of, on a value whose replacement has since been forgotten, or on more of the store than such a read may work over (§7.1). - A removal, from inside a branch, of data written before the branch began; from inside a world, of data the store holds outside it; and `prefer` or `unprefer`, inside either, at a field judged outside it (§7.1.2). - `create_world` naming a world that exists or an open branch; `merge_world`, `discard_world`, `compare_worlds` or a `world` block naming a world that does not exist; `create_world`, `merge_world`, `discard_world` or `compare_worlds` inside a `world` or `branch` block; and, at run time, a `world` or `branch` block opened inside a `world` block, or a `world` block inside a `branch` block (§9.2.9). - `merge_world` naming a world that holds a value at a field one of the store's rules concludes, where the rule does not yield to a recorded value (§7.1.2). - A `branch` block naming a world that exists (§9.2.5). - A literal the store cannot hold — a malformed timestamp, duration, or byte string; a magnitude beyond its type; a name that cannot be an identifier. - A call that exceeds a limit the store enforces. A refuse carries a `refuse_info` (`reason`, `teaching_hint`, `discriminator`). When wrapped in `try: … except refuse as r:`, control transfers to the handler with `r` bound. The exact reason taxonomy is defined by the target service; a conforming implementation MUST surface whatever reason strings the service returns without altering them. ### 10.4 `engine_error` An `engine_error` carries an `engine_error_info` (`reason`, `code`). Caught by `try: … except engine_error as e:`. When a call fails with `engine_error`, nothing the caller wrote is wrong. A condition the caller must fix — a pin to lift, a branch to leave, a literal to rewrite, a call to narrow — is a `refuse` (§10.3) and is caught by `except refuse`, not by `except engine_error`. `engine_error` is **RESIDUAL and promises nothing about a retry.** The call was well formed, no rule declined it, it did not compile-fail, and it still did not complete. That is the whole of what the status carries. `except engine_error` 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. **Because retryability is not this layer's to state.** It is already answered, authoritatively and with the detail a client needs, at the transport: `429` `RATE_LIMITED` and `503` `UPSTREAM_UNAVAILABLE`, and JSON-RPC `-32002` carrying `Retry-After` in seconds. A second retry signal here would be weaker than those and could only disagree with them — and the [MCP Reference](/ref/dke/mcp/) already warns that a client which cannot tell "wait" from "act" will back off when it should not. **What a caller CAN route on**, stated so that "promises nothing" does not read as "tells you nothing": | you want to know | read | |---|---| | did the call complete | `status` — `success` or not | | is my source wrong | `compile_error`, with `diagnostics` | | must I change something before this can work | `refuse`, and switch on `refuse_reason` | | should I wait and repeat | the transport code — never this status | | anything else | `engine_error`, and the message says what happened | Every failure reaches the caller on the status, so routing on the status alone still separates a completed call from a failed one without reading prose. **A store busy with another operation.** A statement can reach the store while another operation is writing to it, for longer than the service waits for that operation to finish. The statement fails as an `engine_error` and keeps nothing. Its message says the store was busy with another operation, and that nothing in the call needs changing. That is the last row of the table working as written: the message says what happened. The service cannot know whether the other operation will have finished by the next call, so this adds no promise to the status. What a program's earlier statements did stands, as it does after any failure. ### 10.5 Diagnostic format (informative) A command-line surface prints diagnostics in the form ``` ::: ``` where `` is `parse_error` or `type_error`. The text after `` is quality-of-implementation; a third-party tool MAY choose any format. ### 10.6 Notes A program that compiles MAY come back with **notes**: statements about the source that did not stop it compiling, reported beside the result rather than instead of it. A note is not an error class. It changes nothing a program does: not whether it compiles, not what it does when run, and not the outcome a call reports. This implementation notes one thing, an `except` handler that can never run (§9.2.4), naming the line of the handler that runs instead. How a note reaches a caller is the service's contract ([MCP Reference §5](/ref/dke/mcp/#response-envelope)). --- ## 11. Versioning and compatibility ### 11.1 Spec version The **DKE Python — Language Specification** is at **alpha · API 1.3 · rev N**: the published surface carries the release stage, the API version it describes and one **revision**, `rev N`, stated on the Version line of every published document, in the footer of every page and by the service's `initialize` answer. The revision moves by one whenever any published document's content changes, whatever the change, and never otherwise, so two copies of the surface that state the same revision are the same surface and the higher of two revisions is the later one. Which revisions changed this document, and how, is recorded in Appendix E, from rev 1 (2026-10-11) forward; a revision absent from that list left this document as it was. Only the current revision is served. The same number is machine-readable: a page carries it as ``, the site's `llms.txt` states it, and the service's `spec` tool reports it as `_meta.revision`. The **API version** is `MAJOR.MINOR`, the second of the three facts the Version line states, and the service reports it as `_meta.api_version` on `initialize` and `tools/list`. From API 1.0, at rev 147 (2026-11-12), it moves so: - **MAJOR** increments when an answer a program or call already got, and got right, changes; when a valid input that was accepted is refused; or when the wire changes in a way a client built against it would notice: a tool, an argument or a field removed or renamed, or a response's shape changed. MINOR returns to 0. - **MINOR** increments on an addition: a name, a built-in, a form, a tool, an argument or an input accepted that was not before, changing no answer a program already got. - **A correction moves neither**: a wrong answer made right, a refusal's wording, or a document's text moves the revision alone. A source declares the MAJOR it is written for (§3.9.1). Within a MAJOR, a source that compiled at an earlier MINOR compiles at a later one and gets the same answers, save where an answer was wrong; across a MAJOR, source compatibility is not guaranteed. A document revision changes no source: an implementation accepts a conforming source by its structure (§4). Each published document also states a **version** of its own, `MAJOR.MINOR.PATCH`, first on its Version line and followed by the revision it last changed in: `**Version:** V, changed in rev R · alpha · API 1.3 · rev N`. It moves only when that document's text changes, by one step of the rule above applied to what the document specifies: **MAJOR** when what it specifies changes an answer, refuses an input that was accepted or changes the wire as that rule says; **MINOR** when it describes an addition; **PATCH** for a correction or a change of wording, which moves a document's version where it moves no API version. The parts after the one that moves return to 0. A document's version is never restarted, and from rev 148 each entry of its Revision history opens with the version that revision gave it. A page carries its document's version as ``. ### 11.2 Reserved for future use The following surface area is reserved and MUST NOT be used: - The names `empty`, `refuse_info`, `engine_error_info` as `type-name` tokens (§5.5). ### 11.3 Compatibility with the target service A DKE Python program is portable across target services that implement DKE's canonical verb surface verbatim. Behaviour differences caused by different target-service surfaces are not language differences; the language layer is the same. --- ## 12. Conformance ### 12.1 Conforming implementations An implementation MAY conform as one of: - **Parser/checker** (static-only): accepts the language defined in §3–§6, rejects with the correct error class per §10.1 / §10.2, produces a typed AST. Sufficient for linters, formatters, syntax highlighters, and language servers. - **Compiler/runtime** (full): in addition, implements the lifecycle the MCP Reference specifies against a target service and the dynamic semantics (§9), satisfying the determinism contract (§9.6) and the termination guarantee (§9.5). A conforming implementation MUST document which class it implements and which spec version it claims. ### 12.2 Conformance suite (informative) A minimal conformance suite for a parser/checker exercises: - Every reserved word from §3.7 in user-identifier position → parse_error. - A tab in indentation and a bad dedent → parse_error. - An empty compile unit (whitespace and comments only) → parse_error. - An unterminated string → parse_error. - A non-XID codepoint at identifier head → parse_error. - A name argument that is not an identifier-shaped bare word → parse_error. - An unbound identifier → type_error. - A `match` on `active_claim` missing the `empty` case → type_error. - A `match` on `active_claim` with an extra case, and one with a duplicate case → type_error. - A `for … in` over an `active_claim` → type_error. - Binding a statement-only verb by assignment → type_error. - A chained comparison `a < b < c` → accepted, with `b` evaluated once. - A `.nonexistent` → type_error. - Every verb in §7.1 in its call form → accepted. - A module body with a top-level data declaration and no `def` → accepted (a module need not declare a script; [Python Reference §9.1](/ref/dke/python/#a-.dpy-file-is-a-module), [§9.7](/ref/dke/python/#published-modules)). - An `import ` statement at module scope → accepted ([Python Reference §9.3](/ref/dke/python/#import)). - A cross-module qualified read `module.Kind(subject).field` → accepted ([Python Reference §9.6](/ref/dke/python/#namespacing)). - A module-qualified construction `module.Kind(subject)`, bound to a name or passed as an argument → accepted (§13.2, [Python Reference §9.6](/ref/dke/python/#namespacing)). - A bare `Kind(subject)` whose class only an imported module declares, with no stored script of that name → type_error (§13.2). - Re-importing an already-loaded module → accepted; it reconciles ([Python Reference §9.5](/ref/dke/python/#re-submitting-reconciles)). - A module top level writing one cell twice from one source over overlapping windows → refused at run time as a conflicting write, naming the cell ([Python Reference §9.5](/ref/dke/python/#re-submitting-reconciles)). - An `import` cycle (a module that imports itself, directly or transitively) → rejected when the module is loaded ([Python Reference §9.3](/ref/dke/python/#import)). - An `import` of a module the store does not hold, then a call into it → refused at the `import`, naming the module; with no use of it → fails there when the program runs ([Python Reference §9.3](/ref/dke/python/#import)). Implementations are encouraged to publish such a suite. --- ## 13. Classes and methods A **class** groups a set of typed **fields** with the **methods** that operate on them, and gives programs a typed handle — an **instance** — for one named member of the class. Classes are an optional surface: a program that declares none is exactly a program without classes. Classes introduce no new types (§5) and no new verbs (§7), and they preserve the termination guarantee (§13.6). One class may **extend** another (§13.8); a method call on a reference whose class is not fixed until run time then runs the method of the instance's actual class — a choice over the fixed, finite set of classes the program declares, never an open-ended lookup. `class` and `return` are reserved keywords (§3.7). A top-level construct is either a script declaration (§4.2) or a class declaration (§13.1). ### 13.1 Class declarations ``` class-decl := 'class' IDENT ( '(' IDENT? ')' )? ':' NEWLINE INDENT ( docstring class-member* | class-member+ ) DEDENT class-member := field-decl | method-def | 'pass' NEWLINE # declares nothing field-decl := IDENT ':' scalar-name NEWLINE | computed-field # a field with an initializer (§13.7) ``` A class declaration is the keyword `class`, the class name, an optional **base class** in parentheses, a `:`, and an indented body of field declarations and method definitions. A class may name a single base class it **extends** — `class Dog(Animal):` — inheriting the base's fields and methods (§13.8); naming more than one base is not supported. A class body holds fields, computed fields (§13.7) and methods (§13.4), and nothing else. It may open with a docstring (§4.1), and `pass` in it declares nothing, so a subclass that adds nothing to its base is written `class Dog(Animal): pass`, and a class with no base may declare nothing at all. `...` alone on its line is the same as `pass` here. A class holds no value of its own, so a class attribute such as `LIMIT = 5` is not a member, and a field takes no default: an `=` after a field's type declares a computed field. A class is declared, and a decorator written, only at the top level of a unit, never inside a class body. A class exists **from the line that declares it**, as in Python. A line that runs as the module runs may not name one declared below it: a construction, a binding annotation or a class-match arm at the top of a unit, a parameter or return annotation, a base, or an argument of `isinstance` (§13.9) or `issubclass` (§13.10). Each is a parse_error, `name 'K' is not defined`, where Python raises that `NameError` as the module runs. A body runs when it is called, so the body of a `def` or a method may name a class declared anywhere in the unit, and two classes may name each other that way. A module statement that reaches such a body above the class, directly or through what it calls, is a type_error in the same words. Both are told at compile, so nothing above them has run, and a call past an `if` that would not have held is refused too. A class body may name its own class, in a method's signature as well. Python raises `NameError` for that signature unless the name is in a string, and here it is read either way. A signature may name any class of the unit in a string (§6.4), which Python never reads as a name. Written both ways in one signature, the class is refused as above. A **field** is `name: type` whose `type` is a **value-class scalar** — a primitive (`string`, `int`, `bool`) or one of the value classes the engine stores as a cell value (`datetime`, `duration`, `real`, `blob`, `scientific`, `complex`). A field is a stored value slot the customer writes and reads. A field declared twice in one class keeps the type written last, as Python's annotations do. A computed field installs a rule, so its name is declared once in a class: a second computed field of that name, or a plain field beside one, is refused. A method may read a field declared below it. A field may instead carry an **initializer** — `name: type = expr` — making it a **computed field** whose value the engine *derives* from the class's other fields and maintains automatically. A computed field's type is `int`, `real`, `bool`, or `complex`, and the customer never writes it directly — a write there refuses. See §13.7. ``` class Sensor: temp: string # methods follow ``` Declaring a class introduces no storage and performs no action; it is a compile-time description the compiler uses to typecheck field access and method calls. Of field access it checks the field's NAME — a field the class does not declare is a type_error (§13.3) — and not the class of a value written to it: the declared type is read for a computed field's initializer (§13.7) and for the field list, and constrains no write. So a class is a closed schema over field names and an open one over field types. ### 13.2 Instances An **instance** names one member of a class, identified by a string: ``` instance-ctor := IDENT '(' expr ')' # the class name applied to a name expression ``` `Sensor(room)` is an instance of `Sensor`, bound by assignment: ``` s = Sensor(room) ``` Constructing an instance records nothing — it is not a write; it yields a typed reference for reading and writing the instance's fields and calling its methods. Reading a field that has never been written yields `empty` (§13.3). Construction also runs no code. A class declares no constructor, and a method named `__init__` is refused (§13.4), so a field gets its value from `remember` once the instance is constructed. An instance is **expressible**. `print`, `str`, `+`, an f-string and `repr` render it, and so does a collection that holds one. It renders as its class and its subject, so `print(Sensor("hall"))` shows ``, and two instances render apart whenever their subjects or classes differ. The class shown is the one the instance was constructed as, which is the class whose methods it runs (§13.8): an instance of `Dog` bound to a name annotated `Animal` renders ``. The subject is quoted the way a string inside a collection is (§8.7), and a class an imported module declares is written with its module, ``. This form matches no CPython output, deliberately. CPython shows an object's memory address, which differs from run to run. A DKE Python render is deterministic, so the same program always prints the same line. A class an imported module declares is constructed through its module ([Python Reference §9.6](/ref/dke/python/#namespacing)). After `import m`, `m.Kind(subject)` is an instance of `m`'s `Kind`. You read and write its fields, pass it to a script, call its methods and test it with `isinstance(x, m.Kind)`, exactly as with an instance of a class this unit declares. A parameter may be annotated `m.Kind`. It accepts an instance of `Kind` or of a subclass `m` declares, and a method called on it runs the method of the instance's own class (§13.6). `m.Kind` is a type wherever a class this unit declares is one (§5.5), so an annotated binding, a collection's element, key or value, and a return type may name it too, and accept what a parameter accepts. A bare `Kind(subject)` whose class only an imported module declares is a type_error that names the qualified form. The exception is a stored script of that name, which the call reaches instead. A write to a bare `Kind` in a unit that does not import the module declaring it does not reach the module's kind: `remember(Kind.s.f, …)` writes a kind of its own named `Kind`, beside the module's ([Python Reference §9.6](/ref/dke/python/#namespacing)). That is a legitimate kind, so the write is accepted. It is also what a writer gets who meant the module's kind and left off the `import`, so `compile` and `run` answer it with a **note** naming the module, after the result and as a `notes` entry. `remember`, `update` and an absence `assert` are noted, once per kind, at the first write; a read is not. A class an earlier compile unit declared is reached the way that unit's scripts are. Where a later unit calls those scripts by their bare names (§6.8), `Kind(subject)` constructs the class, and the instance is used as an instance of a class the later unit declares: its fields are read and written, it is passed to a script, a parameter may be annotated `Kind`, its methods are called and `isinstance(x, Kind)` tests it. Declaring a class that extends it is rejected, because a subclass needs the class's body, which only the unit that declared it has. The name is the weakest binding it has — a class or script the later unit declares, a stored script, and an imported module that declares a class of that name each take it. A class declared again replaces the earlier one for the units compiled after it, and a script already compiled against the earlier class is unchanged. ### 13.3 Field access Given an instance `s` of a class that declares a field `f`: - **Read** — `s.f` yields the current claim for that field: an `active_claim` when the field has a value, or `empty` when it does not. Consume it with `match` (§6.2) or `.value` (§5.2). ``` reading = s.temp ``` - **Write** — `remember(s.f, , )` records a new value for the field together with its provenance source (§7.3). It takes the validity window and `qualifiers=` that `remember` and `update` take on a path (§7.1), whether `s` was bound by construction or is a parameter of the class's type: ``` remember(s.temp, "21", "op1") ``` - **Verbs** — every other verb that takes a `K.s.a` path (§7.1) takes `s.f` as that field's cell, the one `remember` writes: `current(s.f)`, `get(s.f)` and `why(s.f)` read it, as do `verify`, `agreement`, `writers`, `pin`, `unpin`, `prefer`, `unprefer`, `why_not`, `what_needs`, `assert`, `forget` and `would_forget`, through any name that holds an instance, and a hypothesis takes it as each field it supposes and as the field it reads (§15). 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`, `list_attributes` — takes the instance itself as that subject, `caveats(s)`, through any name that holds one, and `list_attributes(s)` takes the as-of time `list_attributes(K.s)` takes. An operand that takes a class and one of its fields — `list_values`, `list_subjects`, `subjects`, a fold — is given the class. An instance's field at either is a type_error. Its message names the form that asks the question — `caveats(s)`, or the class form — and `get(s.f)`, which reads the field. A value is stored with the class it has. A `real` field written with `1 + 0.0` holds `1.0`. Written with the result of a script declared `-> real` that returned `1`, it holds `1`, because arithmetic converts an `int` and a `return` does not (§3.5.7). What decides the stored class is the construct that produced the number, not the field's declaration. Appendix C.9 writes both, and the two compare equal (§8.1). Field access is available on an instance variable and, inside a method body, on the explicit receiver `self` (§13.4). Reading or writing a field the class does not declare is a type_error. A field is written only with `remember`, which names the value's source, so a field written as a target is refused, naming `remember`: `s.f = v`, `s.f += v`, a field in a target list, `a, s.f = 1, 2`, and a field as one target of a chained assignment, `x = s.f = 1`. So is a field of a value no name holds, `f().y = v`, and a write into an element of a field, `s.f[0] = v`, since a field is written whole and never changed in place. Python's grammar reads each as a write, so each is a refusal, and a parse_error later in the program is the answer in its place (§10.1). An **instance variable** is a name bound so that the instance it holds is known where the name is used: by construction, as a parameter of a class's type, by a call to a script declared to return the class, as the loop variable of a `for` over a list of instances or a tuple of them, as an element taken from such a list by index, as a second name for an instance variable, from a conditional expression whose two branches are instances of classes with a common base, or by a binding annotated with the class, `item: Sensor = x`. A list of instances is, here, a display of them, a name bound to one, a parameter of type `list` or `*args: Sensor`, a call to a script declared to return `list`, a list comprehension whose result is its own loop variable over one, a slice of one, or two of them joined with `+`. A script called may be one of the unit's own, one stored before it, or an imported module's, `m.sensors()`. An instance held in a dict or a tuple is followed too, when the container's type is declared by a parameter, an annotated binding or a script's return, when the container is a dict display whose values hold instances or a tuple display that holds one, or when it is taken from one that is: a dict's value by key, a tuple's element by a literal index or by unpacking, and each value of `d.values()` and each pair of `d.items()` a `for` takes. A name bound any other way still holds its instance, and passes it on, and a verb reaches the field's cell through it, as through an instance variable (**Verbs** above). A field read through it, `x.f`, reads the field off the instance as the program runs, and so does one straight off an expression, `xs.pop().n`. A method called through it, or straight off such an expression, `xs.pop().record("21", "op1")`, runs the method of the instance's own class, and a class that holds no such method raises `'Rock' object has no attribute 'record'` when an instance of it is the one called. Its classes' versions of the method answer one type, or the call is a type_error, unless it stands alone on its line. A class match over the name (§13.5) is a type_error that names the annotated binding to write first. The value `d.get(key, default)` returns is the usual case: ``` class Sensor: temp: string def readings(rooms: dict): x = rooms.get("hall", Sensor("none")) item: Sensor = x reading = item.temp ``` No name has to be written between an instance and its member. A field or a method taken straight after a call or an index that would bind an instance variable is taken through the instance it yields, as through that name: `reading = rooms["hall"].temp`, `sensors()[0].record("21", "op1")`. The call or the index is evaluated once, where the expression is. A field read so is a read like any other, bound on its own line. ### 13.4 Methods A **method** is a `def` inside a class body whose first parameter is the explicit receiver `self`. Inside the body, `self.f` names field `f` of the instance the method was called on. ``` method-def := void-method | value-method void-method := 'def' IDENT '(' 'self' ( ',' param-item )* ')' ':' NEWLINE INDENT ( docstring stmt* | stmt+ ) DEDENT value-method := 'def' IDENT '(' 'self' ( ',' param-item )* ')' '->' return-type ':' NEWLINE INDENT docstring? stmt* 'return' expr NEWLINE DEDENT ``` - A **void method** — `def name(self, params):` — declares no return type and its body holds no `return `; it performs actions and yields no value, called as a statement. ``` def record(self, v: string, src: string): remember(self.temp, v, src) ``` - A **value method** — `def name(self, params) -> type:` — declares a return type and is called in expression position. With no `-> type`, a body holding a `return ` gives the method its type, as a script's does (§4.2). Its body is what a value script's may be (§4.2), any statements, an `if`, a `for`, a `match` and an early `return` among them, and it returns a value of the declared type on every path (§6.9); `self`'s fields and the parameters are in scope. A `return` of another type is a type_error that names the method and both types. A field read has type `active_claim`, so a method returning one declares `-> active_claim`, or nothing. A value method returns any type a script may (§4.2), a collection included, so `-> list` is a return type on a method as on a `def`. A value method may also declare `-> value`, and a script may too, by the same rule (§4.2), since Python has one construct for both. A store fold's result is a `value`, which the method's body or its caller narrows with `match`. ``` def label(self) -> string: return "sensor" def combined(self, a: int, b: int) -> int: s = a + b # a local binding … return s * 2 # … then a tail return def sign(self, a: int) -> int: if a < 0: # any statement a script runs return -1 return 1 ``` A method is **called** on an instance with `instance.method(args)` — the `self` argument is supplied by the receiver, not written at the call: ``` s.record("21", "op1") # void method, statement position name = s.label() # value method, expression position ``` A method's arguments bind as a script's do (§4.2): by position or by keyword, an omitted one takes the parameter's default, and a parameter list holds the markers of §4.2, each with its own refusal. A keyword-only or a variadic parameter is bound by the method's stored script, resolved from the receiver's class as any call is. A value method may also be called as a statement, `s.label()`. It runs, and its value is discarded, as a call statement discards a value script's (§4.3). A method call is resolved from the receiver's class. When that class is fixed where the call is written, exactly one method body applies. When the receiver is held at a base-class type — a base-class parameter, or an element of a list of mixed subclasses — the call runs the method of the instance's **actual** class (§13.8); because a class hierarchy is closed and finite, the candidate bodies are a bounded, program-declared set, not an open-ended lookup. Methods obey **compile-before-call** exactly like scripts (§6.8), so a method may call one written below it; method recursion — direct, mutual, or through an override and `super` (§13.8) — is not expressible, so the call graph is acyclic (§13.6). Argument count and types are checked against the method's declared parameters. A `*` before a positional argument of a method's call spreads its operand's elements across the call's arguments, as in a script's call (§8.15), and the call is checked as one is: given `def m(self, x: int, y: int)`, `b.m(*xs)` binds `x` and `y` from `xs`. The receiver counts as a positional argument where Python counts it, so `b.m(*[1, 2, 3])` is refused as the program runs with `Box.m() takes 3 positional arguments but 4 were given`. A refusal names the method as Python does, by the class whose body holds its `def`: a method `Sub` inherits from `Box` is `Box.m`, and so is the version a `super()` call reaches. A lone spread of no collection names it by its module too: `__main__.Box.m() argument after * must be an iterable, not int`. A method of a class another module declares is named by the class it is called on, so one that class inherits reads `Sub.m()` there, where Python reads `Box.m()`. **Special methods.** A method whose name begins and ends with two underscores — `__init__`, `__str__`, `__eq__`, `__len__` and the rest of Python's special methods — is a parse_error with a teaching hint. Python calls those methods on its own, on construction, on `print`, on `==`, and DKE Python calls none of them, so a method of that name would never run. Each hint names the DKE way to do what the method was for: - `__init__` — construction runs no code (§13.2). Give a field its value with `remember(s.f, , )` after constructing the instance, or in an ordinary method called then. - `__str__` and `__repr__` — an instance already renders, as its class and its subject (§13.2). To show something else, read a field into a name and build the string from it, or return it from an ordinary value method. - `__eq__`, `__ne__`, `__lt__`, `__le__`, `__gt__` and `__ge__` — instances are not compared or ordered by an operator (§8.1). Compare a field of each, or test the class with `isinstance`. `x in xs` over a list of instances already compares them by subject and class. - Any other — give the method an ordinary name and call it by that name. ### 13.5 Dispatch by class When code must branch on which class an instance belongs to, it uses a `match` whose scrutinee is a bare instance variable and whose `case` arms name classes: ``` class-match := 'match' IDENT ':' NEWLINE INDENT class-case+ default-case? DEDENT class-case := 'case' IDENT ':' ( simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT ) default-case := 'case' 'default' ':' ( simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT ) ``` ``` 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, and whose arms each name another class, with a catch-all last if any, is a **class match**; any other `match` is read by §6.2. Inside a `case :` arm the instance is treated as that class, so its fields and methods resolve to that class. Written with no parentheses the arm set is **closed and exhaustive**: it MUST cover the instance's class — or, under inheritance (§13.8), every class the instance could belong to — or provide a `case default:` arm, and a class match that is neither is a parse_error. `case ():`, Python's class-pattern spelling, is the same arm, and is a test: a class match with any arm written so runs no arm when none holds, as in Python. A `case` names its class as a construction does (§13.2, [Python Reference §9.6](/ref/dke/python/#namespacing)): a class the unit declares by its bare name, a class an imported module declares as `.`. Class patterns over any other value, and what their parentheses hold, are §6.2's. When the instance's class is fixed where the match is written, the arm is chosen there; when the instance is held at a base-class type, it is chosen at run time, from the same fixed arm set. Either way the arm chosen is the first, in source order, whose class is the instance's actual class or a base of it (§13.8) — a bounded selection over the program's classes, never an unbounded lookup. ### 13.6 Termination Classes preserve the termination guarantee of §9.5. Method dispatch — direct calls (§13.4), class matches (§13.5), and the dispatch on an instance's actual class under inheritance (§13.8) — always selects from the fixed, finite set of classes and methods the program declares; even when the actual class is not known until run time, the selection is over that bounded set, so it introduces no unbounded lookup. Methods are non-recursive by construction; iteration remains the bounded `for … in` of §6.3. Every method body is therefore a finite, non-recursive program over bounded iteration. ### 13.7 Computed fields A **computed field** is a field declared with an initializer: ``` computed-field := IDENT ':' computed-type '=' expr NEWLINE computed-type := 'int' | 'real' | 'float' | 'bool' | 'complex' ``` Its value is not written by the customer; the engine **derives** it from the class's other fields and keeps it current as those fields change. A computed field's declared type is `int`, `real`, `bool`, or `complex`. ``` class Order: price: int qty: int total: int = price * qty # a computed value big: bool = total > 1000 # a computed flag ``` Given `Order.o1.price = 10` and `Order.o1.qty = 5`, reading `Order.o1.total` yields `50`; writing `Order.o1.price = 200` re-derives it. The customer writes only `price` and `qty`; `total` and `big` are maintained automatically. A computed field reads back like any field (`current`, §7.1) with a `derived` provenance in place of a source. **A write to a computed field refuses.** `remember` or `update` at a computed field, or a supposition of one (§15), is a refuse (§10.3) whose reason names the fields the value is derived from — write those instead. Nothing is recorded, so the field keeps its derived value and `current` reads it straight after the refused write. `forget` at a computed field is not refused: it removes only what was recorded there, and the derived value is untouched. **A computed field is not declared over recorded values.** Installing a class whose computed field already holds values someone recorded refuses too, and the reason names those values: they would stand beside the derivation as a disagreement (§7.6) that no write could clear. Forget them, or give the computed field a name of its own. The refusal changes nothing recorded. **The initializer.** Every identifier in the initializer must name another field of the same class **declared earlier** (a computed field may build on an earlier computed field), and the initializer must reference at least one such field. The form depends on the declared type: - An **`int`** field is an arithmetic expression over `int` and `bool` fields and integer literals using `+`, `-`, `*`, `%` (§8); a `bool` field is the number it is (§8.1). Division (`/`) is not written in an `int` field — its result may be fractional; declare the field `real`. - A **`real`** field is an arithmetic expression using `+`, `-`, `*`, `/`, `%` over `int`, `bool` and `real` fields and numeric literals (§8.3). Division is true division here as everywhere (§8.3): it yields a `real` whether or not it divides evenly, so `n / 2` over `4` is `2.0`. - A **`complex`** field is an arithmetic expression using `+`, `-`, `*`, `/` over `int`, `bool`, `real` and `complex` fields, numeric literals and imaginary literals (§8.3). It computes as a program does: a number beside a complex number is read as the complex number with a zero imaginary part, and the result is a `complex`, so `gain * 2` over `1+2j` is `(2+4j)` and `gain + 1j` over the `int` `3` is `(3+1j)`. An expression none of whose operands is complex derives the number it computes, which the field holds as a declared `complex` holds a number (§3.5.15). The remainder `%` and the power `**` are not written in one: a remainder has no meaning on a complex number, and no computed field derives a power. A subject whose expression divides by zero holds no value in the field, and a part past the range of a `real` is `inf` or `-inf`, as a program computes it. - A **`bool`** field is a single ordered comparison — `<`, `<=`, `>`, `>=` — of one earlier field against a numeric literal or another earlier field. Its left side is a single field: to compare a computed value, declare that value as its own field first (`total: int = price * qty`, then `big: bool = total > 1000`). The comparison ranks two values of one kind, as a program does: numbers (`int`, `real`, `bool`) against numbers, a `string` against a `string`. A complex number has no order (§3.5.15), so a `bool` field that compares a `complex` field or an imaginary literal is refused where it is declared. Where a field it reads holds a value it cannot rank against the other side — a blob, which has no order, or a value of another type, such as a string recorded where the field declares an `int` — the comparison has no answer, so the field holds neither `True` nor `False` for that subject, and `why_not` on it says so. Because every reference points at an *earlier* field, a class's computed fields form no cycle. Across compiles they can: a later compile may declare the class again with a computed field reading one that an earlier compile computes from it, and a rule may conclude into a field a computed field reads. Such a cycle is refused when it passes through a computed field whose initializer applies an operator (`total: int = price * qty`). A computed field that would close it with what the store already holds is refused at the compile that closes it, the refusal names the field and says its value would depend on itself, and nothing that compile declares is stored; a rule that would close it is refused as §14.5 says. A cycle whose computed fields only copy one other field (`a: int = b`) or compare (a `bool` field) is accepted: a copy holds a value some subject already holds, and a comparison holds `True` or `False`, so the cycle settles. A computed field participates in `match` (§6.2) and expressions (§8) exactly as a written field of the same type would. **Termination.** Computed fields preserve the termination guarantee of §9.5: a derivation is a finite, acyclic expression over the class's own fields, evaluated once per change; it introduces no loop and no recursion. ### 13.8 Inheritance A class may **extend** one other class — its **base class** — named in parentheses after the class name: ``` class Animal: sound: string def speak(self): remember(self.sound, "generic", "op1") class Dog(Animal): breed: string def speak(self): super().speak() remember(self.breed, "collie", "op1") ``` A class extends **at most one** base (single inheritance). Naming more than one — `class C(A, B):` — is a compile-time error, as is naming a base that is not a class declared earlier in the program, or naming the class itself. Empty parentheses, `class A():`, name no base: that is the class `class A:` declares, as in CPython. **Inherited members.** A subclass has every field and method of its base, plus those it declares. `Dog` above has `sound` and `speak` from `Animal` and its own `breed`; an inherited method reads and writes the base's fields on the subclass instance. Inheritance is transitive: a class three levels deep has the members of every class above it. **Overriding.** A subclass may declare a method with the same name as one it inherits; the subclass's version then applies to instances of the subclass. Above, `Dog.speak` overrides `Animal.speak`. **`super`.** Inside a method, `super().(args)` runs the base class's version of `` on the same instance — the inherited step — which an override can then extend. It is always the base's version, never an override's. When `` returns a value, `super().(args)` is that value and stands wherever a value does, as in `return "z" + super().label()`. When it returns nothing, the call is a statement of its own. `super` is available only inside a method body, and names the base of the class that declares the method, also where a subclass inherits or overrides that method. (Constructor chaining — a `super().__init__(...)` call — is not part of the language: construction takes only the instance name and runs no code (§13.2), and a method named `__init__` is refused (§13.4).) **Substitutability.** A subclass instance is accepted wherever its base class is expected: as a parameter typed with the base, as a binding used at the base type, as an element of a list whose element type is the base, and as the value returned by a script or method declared to return the base. No cast is written — a `Dog` value is an `Animal` value. A class that is not the named one and does not descend from it is refused where the name is written, and refused when the program is compiled rather than when it runs. A list **written** with instances of several subclasses takes their nearest common base as its element type, so `[Dog(d), Cat(c)]` is a `list` and may be passed where one is named. A list written where a type is declared for it takes that type wherever each element fits it. That holds at an annotated binding, as an argument, at a `return` and as a parameter's default, so `[Dog(d)]` may be passed where a `list` is named. A dict written with values of several subclasses takes their nearest common base as its value type, so `{"d": Dog(d), "c": Cat(c)}` is a `dict`, and a dict written where a type is declared takes that type in the same positions. A display written inside a display joins so too, at any depth: `[[Dog(d)], [Cat(c)]]` is a `list>`. Instances of classes that share no base class may be held in one list, dict or tuple, 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, since no class says which field is meant. A list a name already holds does not widen: a `list` is not a `list`, for the reason §8.14 gives — the list is one object its holders share, and a holder reading it at the wider type could put a `Cat` where the narrower one reads `Dog`s. **Dispatch under inheritance.** When a method is called on a reference whose class is fixed where the call is written, exactly one body applies (§13.4). When the reference is held at a base-class type — a base-typed parameter, or an element of a list of mixed subclasses — the call runs the method of the instance's **actual** class: ``` class Cat(Animal): def speak(self): remember(self.sound, "meow", "op1") def herd(a: Animal): a.speak() # runs Dog.speak, Cat.speak, or Animal.speak, # according to a's actual class def demo(d1: string, c1: string): animals = [Dog(d1), Cat(c1)] # a list of mixed subclasses for a in animals: a.speak() # each element runs its own class's speak ``` Value-returning methods (§13.4) dispatch the same way. Where the class of the instance a call reaches holds a method of that name that returns nothing, as an override written `def m(self): pass` beneath one that returns a value, the call runs that method, and reading its value is then refused as the line runs, naming the method as CPython names it: ``method `B.m` returns nothing — it cannot be used as a value``. CPython hands the caller `None`, which DKE Python has no value for. No `except` handler takes the refusal, since CPython raises nothing there, and a `finally` runs. A call whose arguments that method does not take is refused the same way without running it. This holds wherever the read is written: in a method a subclass inherits, read from an instance of the subclass, in a script written above the class, and in an element's call in a comprehension. A call whose arguments the method of the class it reaches does not take, where that method returns a value or the call stands alone as a statement, 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 class whose method takes them runs it as written. Where no class a read can reach holds a method that returns a value, or no class's method takes a call's arguments, the program is refused where it is compiled. Because a program's classes form a closed, finite hierarchy, the set of methods any one call can reach is bounded and known from the program text; there is no open-ended lookup, and termination (§13.6) is preserved. **Reading a member under a base type.** Through a base-typed reference you may read or write any field, and call any method, the **base** declares — these are present on every instance. Reading a field or calling a method that only a *subclass* declares first requires establishing the instance's class, with `isinstance` (§13.9) or a class match (§13.5); otherwise it is a type_error. **Class match under a hierarchy.** A class match (§13.5) on a base-typed instance is resolved at run time; a `case :` arm matches that class or any of its subclasses, first match winning. Written with no parentheses, the arm set stays closed and exhaustive over the hierarchy — cover every case or provide `case default:`. ``` def classify(a: Animal): match a: case Dog: print("kind=dog") case Cat: print("kind=cat") case default: print("kind=other") ``` ### 13.9 `isinstance` `isinstance(x, C)` reports whether the instance `x` belongs to class `C` — **true** when `x`'s class is `C` or any subclass of `C`, **false** otherwise. It returns a `bool`. The second argument may also name a **type**, as the language spells it — `int`, `bool`, `real`, `string`, `datetime`, `duration`, `blob`, `scientific`, `complex`, `null`, `list`, `dict`, `set`, `tuple` or `slice` — and then tests a value of any type: `isinstance(1, int)` is `True`, and every value is an `object`. `str` and `float`, the conversions (§8.13), name the `string` and `real` they build, as in CPython. A `bool` is an `int` here, as a `case int:` arm takes one (§6.2), and an `int` is not a `bool`; `list` tests a constructed list, and a read result is not one. The argument may be a parenthesized tuple of classes and types, or a `|` union of them, and is true when any one is: `isinstance(x, (int, real))` and `isinstance(x, int | real)`. An empty or nested tuple is a parse_error. An argument left out, as between the commas of `isinstance(x,,)`, or begun with a mark or a word no value begins with, as in `isinstance(x, :)`, is a grammar error (§10.1). A class is tested over any value, and a value that is no instance of it is `False`: `isinstance(1, Dog)`. ``` if isinstance(d, Dog): print("d is a Dog") if isinstance(d, Animal): # a Dog is an Animal print("d is an Animal") if isinstance(an, Dog): # a plain Animal is not a Dog print("an is a Dog") else: print("an is not a Dog") ``` When `x`'s class is fixed where the call is written, the result is a compile-time constant; when `x` is held at a base type, it is decided from the instance's actual class. A type is tested on the value when the call runs, never before: a name declared `int` may hold a `bool`, and `isinstance` of it with `bool` is `True`. Inside the taken branch of an `isinstance` test the reference is treated as the tested class, so its subclass-only fields and methods become accessible (§13.8). The test is the whole condition, and the narrowing ends with the branch: the `else`, and everything after it, see the declared class again. A tuple or union narrows to the nearest class its alternatives share — `(Puppy, Hound)` to `Dog` — and alternatives sharing no class, or a type named among them, narrow nothing. A test against a class the reference already has, or against one of its bases, leaves the reference alone. A name holding a `value` is narrowed by a test against types, to the tested class, and there the test need not be the whole condition: an `and` narrows what follows it, and an `or` of two tests narrows to what they join to. Where the test fails, the `else` reads such a name at the classes it leaves (§5.6). ### 13.10 `issubclass` `issubclass(A, B)` reports whether the class `A` is `B` or a subclass of `B` — **true** when `B` is `A` itself or a base `A` reaches through its chain of bases, at any depth, **false** otherwise. It returns a `bool`. A class is a subclass of itself, as in Python. ``` class Animal: name: string class Dog(Animal): pass print(issubclass(Dog, Animal)) # True: a Dog extends Animal print(issubclass(Dog, Dog)) # True: a class is a subclass of itself print(issubclass(Animal, Dog)) # False: an Animal need not be a Dog ``` Either argument may name a type instead, as `isinstance`'s second argument does (§13.9), and `str` and `float` name the `string` and `real` they build. A type is a subclass of itself and of no other type, except that a `bool` is an `int`, as it is everywhere in this language: `issubclass(bool, int)` is `True` and `issubclass(int, bool)` is `False`. A class is never a subclass of a type, and a type never one of a class. The second argument may be a parenthesized tuple of classes and types, or a `|` union of them, and is true when any one is: `issubclass(Dog, (Cat, Animal))` and `issubclass(bool, int | real)`. An empty or nested tuple is a parse_error, as it is for `isinstance`. An argument left out, as in `issubclass(,)`, is a grammar error, as it is for `isinstance`. A class is not a value in this language, so each argument is a name written where the call is. The first names one class or one type, and anything else there — an instance, a name bound to a value, a call, a tuple or a union — is a parse_error that says so: `issubclass(x, int)` is a parse_error. To ask which class an instance belongs to, use `isinstance` (§13.9). A class either argument names must be declared above the call, or be one an imported module declares, written `m.Kind`, as a class `isinstance` names must be. Both being fixed where the call is written, the result is a compile-time constant. A keyword argument is a type_error. --- ## 14. Standing rules A **standing rule** is a named statement that derives facts automatically. It states a condition over the stored data and a conclusion to record whenever that condition holds. Once defined, a rule's conclusion **always reflects the current data**: it is in force exactly while its condition holds, so a derived fact is never stale. A computed field (§13.7) derives one field of one class from that class's own fields; a standing rule is written on its own and is named. ### 14.1 The rule block A rule is a `def` marked with the `@rule` decorator and a name no other declaration in its unit holds (§4.2), then a `for` clause naming the subjects it ranges over, an `if` clause stating the condition, and an indented conclusion: ``` @rule def needs_review(): for o in Order: if o.total > 1000 and absent(o.shipment): o.review = True ``` ``` rule-decl := '@rule' ( '(' 'kind' '=' rule-kind ')' )? NEWLINE 'def' IDENT '(' ')' ':' NEWLINE INDENT 'for' subject-binding ( ',' subject-binding )* ':' NEWLINE INDENT 'if' premise ( 'and' premise )* ':' NEWLINE INDENT conclusion NEWLINE DEDENT DEDENT DEDENT subject-binding := IDENT 'in' kind-name kind-name := IDENT # the kind whose fields the name reads rule-kind := '"definition"' | '"causal"' | '"empirical"' | '"policy"' | '"heuristic"' ``` The `for` clause gives each subject the rule is about a name, and the kind whose fields it reads under that name (`for o in Order`). Every name the rule uses — in a premise or in its conclusion — is one of those subject variables. The `@rule` marker distinguishes a rule from an ordinary script; `for`, `in`, `if`, and `and` read here exactly as they do elsewhere in the language, and `absent` names one of the rule surface's three negative premises (§14.2). **The premises, not the kind, say which subjects a name ranges over.** A name whose fields a ranging premise reads (§14.5 lists them) ranges over the subjects recorded with those fields. A name reached only through a join or a relation premise ranges over the subjects the join or the relation names — the values that field holds, the pairs that relation holds — 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` although no `N` named `w` is recorded. A rule that wants only recorded `N`s reads one of their fields: with `and y.name == y.name` added, it concludes only for a `y` recorded with a `name`. A rule may bind **more than one** subject, separated by commas, and then relate them with a join premise (§14.2). Each name must be distinct. The bindings are a conjunction and carry no order: they are separated by commas rather than written as nested `for` blocks precisely because nesting would suggest an iteration sequence, and a rule states a condition over the whole store rather than describing a walk through it. A nested `for` inside a rule is not accepted. ``` @rule def taint_spreads(): for f in Fn, c in Call, g in Fn: if f.tainted == True and c.caller == f and c.callee == g: g.tainted = True ``` Read plainly: whenever a tainted function `f` is the caller of some call `c`, and `c`'s callee is `g`, then `g` is tainted too. Because the conclusion writes the same field the first premise reads, the rule applies to its own results, and the taint reaches everything the call graph connects — not to a fixed number of steps. Two of the three subjects are named by the join rather than compared: `c` and `g` are found through `f`, which is what lets one rule follow a chain. **A rule's kind.** `@rule(kind="causal")` says what kind of relation the rule states, one of five: `"definition"` (the conclusion is what the premises mean), `"causal"` (what the premises describe brings the conclusion about), `"empirical"` (the two have been observed together), `"policy"` (the conclusion is decided rather than discovered) and `"heuristic"` (a rule of thumb). The kind is for the reader. It changes nothing the rule concludes, holds or refuses: a rule written with a kind and the same rule written without one conclude the same claims. It reads back on the rule's conclusions as `.rule_kind` (§5.2), in the rule listing (§14.7) and in the module's information. The five words are the same in every language, as `trust`'s are; the keyword `kind` 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.dpy — a rule says what kind of relation it states, and its conclusion reads it. @rule(kind="policy") def needs_review(): for o in Order: if o.total > 1000: o.review = True def show_kind(): remember(Order.o1.total, 1200, "till") r = current(Order.o1.review) print("review: " + str(r.value) + ", " + r.rule_kind) ``` ``` OK script show_kind(): 1 claim written, 1 print remember Order.o1.total = 1200 print review: True, policy ``` ### 14.2 Premises The `if` clause is one or more **premises** joined by `and`; the rule holds only when every premise holds: ``` premise := comparison | equality | join | field-cmp | relation | absence | not-asserted | known-absent | nonexistence | source-test | trust-test | text-test comparison := IDENT '.' IDENT ord-op ( '-'? ( INT | REAL ) | SCIENTIFIC | STRING ) # o.total > 1000, o.price > 19.99, # o.mass > scientific("1.0e4"), o.name < "m" equality := IDENT '.' IDENT eq-op literal # o.lic == "MIT", o.total != 5 join := IDENT '.' IDENT eq-op IDENT # c.caller == f field-cmp := IDENT '.' IDENT ( eq-op | ord-op ) IDENT '.' IDENT # o.delivered > o.deadline relation := IDENT '.' IDENT '.' IDENT eq-op literal # Reach.x.y == True absence := 'absent' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # absent(o.shipment), absent(Gap.x.y) not-asserted := 'not_asserted' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # not_asserted(b.flies) known-absent := 'known_absent' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # known_absent(p.chip) nonexistence := 'none' '(' IDENT 'in' IDENT ':' premise ( 'and' premise )* ')' # none(c in Call: c.caller == f) source-test := 'source' '(' IDENT '.' IDENT ')' ( eq-op STRING | '==' IDENT ) # source(r.temp) == "lab", # source(r.temp) == s trust-test := 'trust' '(' IDENT '.' IDENT ')' eq-op ( '"asserted"' | '"derived"' ) # trust(s.label) == "derived" text-test := text-cell '.' ( 'startswith' | 'endswith' ) '(' text-part ')' | text-part 'in' text-cell # r.path.startswith("/srv/"), # t.name.endswith(".test"), "admin" in a.role text-cell := IDENT '.' IDENT ( '.' IDENT )? text-part := STRING | IDENT '.' IDENT eq-op := '==' | '!=' ord-op := '<' | '<=' | '>' | '>=' literal := STRING | '-'? ( INT | REAL ) | SCIENTIFIC | complex | BLOB | 'True' | 'False' # §3.5; the same set `suppose` takes (§15) complex := '-'? IMAG | '-'? ( INT | REAL ) ( '+' | '-' ) IMAG # 2j, 1+2j, -0.5-1.5j (§3.5.15) ``` **Premises that name the same field through the same name read one value of it.** A field can hold more than one value at once, since two sources that disagree each hold one there (§7.6). Premises that name one field (`r.t` in `r.t > 30 and r.t < 40`) are tested together against **one** of its values, never each against whichever value suits it. A reading whose values are `50` and `20` therefore does not satisfy `r.t > 30 and r.t < 40`: each value meets one bound and neither meets both. Record a `35` and the premises hold through that value. The rule covers every premise that names a field — a comparison, an equality, a field comparison, a join, a source or trust test — so a join names the subject that the value the other premises accepted refers to, and a source test is about where that value came from. A fold over the field in the conclusion folds only the values the premises accepted (§14.3). Where several values pass, the rule holds through each of them (§14.4). - A **comparison** (`o.total > 1000`) holds when the field, read as a number, stands in the given order to the numeric literal. The bound may be **negative** (`r.temp < -10`), so a threshold over a signed quantity — a temperature, a balance, an elevation — is expressible. It need not be a whole number either: `o.price > 19.99`, `r.ratio > 0.75` and `s.temp > 36.6` are thresholds a rule can state. What is compared is the **number**, not the way either side was written, so a fractional bound reads a whole-numbered field and a whole bound reads a fractional one. The bound may also be a **quoted string** (`o.name < "m"`), in which case the field is read as text and ordered **alphabetically**, by Unicode code point. The two readings do not mix: a text bound compares only against fields holding text, and a numeric bound only against fields holding numbers, so a field carrying both never answers by accident. A bound written in **scientific notation** (`o.mass > scientific("1.0e4")`) is a numeric bound and orders by **magnitude**, so it reads a field recorded in scientific notation the way a plain numeric bound reads a plain number — `scientific("9.0e2")` is below a `scientific("1.0e4")` bound, whatever the two look like side by side. - An **equality** (`o.lic == "MIT"`) holds when the field holds exactly that value. It takes the same literals a conclusion takes: 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 are the two literals that carry `==` and `!=` and **not** the ordered operators: `<`, `<=`, `>` and `>=` say nothing about one, and a rule that writes one is refused where it is written. A complex number is written as a `case` label writes one (§6.2): an imaginary literal, or a number, `+` or `-`, and an imaginary literal, either with a leading `-`. An equality against one holds when the field holds a value equal to it, as a program's `==` decides (§8.1): that complex number, both parts equal, or a number equal to it. So `z == 1+0j` holds for a field holding `1`, `1.0` or `True`, and `z == 1` for one holding `1+0j`. A complex number with a non-zero imaginary part equals no number, and a number in scientific notation is not equal to a complex number, here as in a program. Equality over numbers is over the **number**, so `o.total == 5` holds for a field recorded as `5` and for one recorded as `5.0`; a field holding the *text* `"5"` is a string, not the number, and does not hold. - An **inequality** (`o.total != 5`) is the exact complement of `==` over values that are **recorded**: it holds when the field holds some value that is not the given one — including a value of another kind, so a field holding the text `"5"` does hold `o.total != 5`, the same reading that keeps it out of `o.total == 5`. A field with **no value recorded**, or one recorded as explicitly empty, holds NEITHER `==` nor `!=`: comparing against nothing is not a comparison. Ask that question with `absent(...)`, which is about whether a value exists rather than what it is. - A **join** (`c.caller == f`) holds when the field holds the **subject of another subject the rule binds**. The right side is a subject name from the `for` clause rather than a value, which is how a rule relates two subjects: it reads a field as a reference and follows it. A field written with `remember` holds a subject exactly when the value recorded there is that subject's name, so a join and a plain equality are the same test — what differs is that a join says which subject the answer is about, and so lets the conclusion be about that subject instead. A join with `==` also **ranges** the subject on its right, over the subjects the field names whether or not anything of its kind is recorded for them (§14.1), which is what allows a conclusion about a subject no premise compared. `!=` does not: it says which subjects are excluded, which names no definite set of subjects (§14.5). The right side of a join is a subject. To compare against a second **field**, write that field — see the next entry, which is a different premise. - A **field comparison** (`o.delivered > o.deadline`) holds when the two fields' stored values stand in the given order. Both operator families apply: the ordered ones compare as §8.1 does — numerically between two numbers, chronologically between two instants, by length between two durations, alphabetically between two texts — and `==` / `!=` compare for equality, as a program's `==` does, so a field holding `1+0j` equals one holding `1`. Two values of different classes stand in no order, so a duration against a number or a number against a text does not hold; a `bool` is a number here, as it is in a program. One token tells a join from a field comparison: a bare subject on the right is a join, a subject followed by a field is this. The two fields may belong to the **same** subject or to different subjects the same `for` binds: ``` @rule def late(): for o in Order: if o.delivered > o.deadline: o.late = True @rule def over(): for c in Charge, l in Limit: if c.amount > l.cap: c.over = True ``` **A field comparison requires both fields to have a value.** A subject with no `deadline` recorded has nothing to be late against, so the rule does not conclude about it — the same reading `==` and `!=` already have, and `absent(...)` remains the way to ask whether a value exists at all. The right side names one subject and one field. A **specific** subject (`Order.o1.total`) cannot be named there: a rule ranges over the subjects it binds, and asking about one named subject is what `current(...)` answers. - A **text test** asks about **part of a text**, in the spellings a program uses for the same question: `r.path.startswith("/srv/")` holds when the field's text begins with the part, `t.name.endswith(".test")` when it ends with it, and `"admin" in a.role` when it holds the part anywhere. A field holds one value at a time, so `in` over a field asks for a substring, as it does over a string in a program. The part is a quoted string or a field, written `var.field` for a subject the rule's `for` binds, on either side of `in`: ``` @rule def admin_path(): for r in Request: if "admin" in r.path: r.admin = True @rule def served(): for r in Request: if r.path.startswith("/srv/") and absent(r.admin): r.public = True @rule def under(): for r in Request, s in Share: if r.path.startswith(s.prefix): r.shared = True ``` A text test cannot be **negated**: `not in`, and `not` before `startswith` or `endswith`, are not premises, and a rule that writes one is refused. Ask the positive question in a rule of its own, as `admin_path` does, and test the absence of what it concludes, as `served` does. The test is **code point by code point**: no case folding, no normalization, no locale, so `"Admin"` does not hold `"admin"`, as in a program. The **empty string** is a part of every text, at its start, its end and within it. Only a **text** holds a part: a number, a datetime, a duration or any other value holds none, even where its written form contains the characters, and a field with no value holds none either. A part that is a field holds for a text only when that field holds a text too. The tested text may be a pair's field, written as a relation premise writes it (`Name.x.y.startswith("a")`). A text test says which subjects the rule applies to, as a comparison does, and it reads the same value of its field as every other premise on that field (above). A test asks about **one** part: a tuple of parts, which `startswith` takes in a program, is refused, and so is a window after the part (`startswith("a", 1)`); write a premise for each part a rule needs, and a second rule for a part that would do instead. There is no regular-expression premise. - An **absence** (`absent(o.shipment)`) holds when the subject has **no value recorded** for that field. It is one of the premise grammar's **three** negative forms — `absence`, `not-asserted` and `nonexistence` — and it means the plain absence of information, not a field recorded as `false` or empty. A field asserted absent with `assert(K.s.a, src)` holds a recorded absence, so `absent(...)` does not hold over it — and neither does `not_asserted(...)`, since a person recorded it. `known_absent(...)` is the premise that holds over it. - A **not-asserted** (`not_asserted(b.flies)`) holds when **nobody has recorded a value** for that field. It is the narrower of the two that ask about ONE cell (the third, `none(...)`, asks about a whole relation): `absent(...)` asks whether the cell is empty, this asks only whether a person put something there, so a value a rule concluded leaves it holding while `absent(...)` goes false. Like an absence it is shape-only — there is no `not_asserted(b.flies, True)` form, because *did a person record this particular value* is a different question and is answered by reading the field. **This is what lets a rule state a default and yield to a recorded fact.** *Birds fly, unless someone has said otherwise about this bird* is one rule: ```python @rule def birds_fly(): for b in Bird: if b.species == "avian" and not_asserted(b.flies): b.flies = True ``` Record `b.flies = False` for a penguin and the rule stops concluding about that bird — not because the conclusion was overwritten, but because its premise stopped holding. Remove that record and the default returns. No conflict is created and nothing has to be ordered, which is the difference between a default and a fight between two sources. **A rule may name its own conclusion in `not_asserted(...)`, and this is the one place that is allowed.** `absent(b.flies)` in the rule above would be self-cancelling — the rule's own conclusion would falsify its own premise — and is refused with a message pointing here. `not_asserted(...)` reads only what people wrote, and a rule's conclusion is not that, so the question stays answerable no matter how many times the rule fires. - A **known absence** (`known_absent(p.chip)`) holds when a source has **recorded the field as known to be absent**, with `assert(K.s.a, src)`. It is the positive form beside the two negatives above, and the three tell a field's three states apart: `absent(...)` holds when it has no value at all, `not_asserted(...)` when nobody recorded one, and `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 says which subjects the rule applies to (§14.5). It reads the same claim as every other premise on its field, so a source test beside it asks who recorded the absence: ```python @rule def vet_found_no_chip(): for p in Pet: if known_absent(p.chip) and source(p.chip) == "vet": p.needs_chip = True ``` This concludes for a pet the vet recorded with no chip. It does not conclude for one where the owner recorded the absence and the vet recorded a number, whichever premise is written first. A recorded absence has no value, so a conclusion may not copy or fold the field this premise tests (§14.3). - **Over a pair.** Each of the three also takes a pair, written as a relation premise writes one: `absent(Gap.x.y)` asks about the field `y` names on the subject `x` of `Gap`, both names bound by the rule's `for`. It asks about that one cell exactly as the one-field form does, so `absent(Gap.x.y)` holds when nothing is recorded there, `not_asserted(Gap.x.y)` when nobody recorded anything there, and `known_absent(Gap.x.y)` when a source recorded that there is nothing. A value recorded at a pair later stops a conclusion drawn from its absence, and removing it lets the conclusion return. `not_asserted` over a pair states a default for every pair and yields to the ones a person records: ```python @rule def default_priority(): for x in Task, y in Task: if x.open == True and y.open == True and not_asserted(Priority.x.y): Priority.x.y = "unranked" ``` `absent(...)` and `not_asserted(...)` over a pair say which subjects the rule applies to no more than the one-field forms do, so another premise must name both (§14.5); `known_absent(...)` over a pair names both. Because the field is whichever one `y` names, a rule that tests `absent(Gap.x.y)` may not conclude any field of `Gap`: it could record the very field it tested. The same pair concluded is answered with `not_asserted(...)`, as for one field. - A **nonexistence** (`none(c in Call: c.caller == f and c.tainted == True)`) holds when **no subject of that kind satisfies every premise inside it**. Where an absence asks about one cell of one subject, this asks about a whole set — "none of its callees is tainted", "no order of this customer is unpaid" — which is the difference between naming a fact and quantifying over a relation. The inner premises are the ordinary premise grammar and may name ONE of the outer rule's subject variables, so `c.caller == f` is what ties the inner set to the subject being concluded about. **It is answered for each subject of the variable it names.** That variable need not be the one the rule concludes about. A rule over `for r in Req, u in User:` that tests `r.owner == u and none(f in Flag: f.user == u)` and concludes `r.open = True` asks, for each request and each of its owners, whether that owner has no flag — so a request owned by two users is open when either of them has none. A `none(...)` whose inner premises name two of the outer rule's subject variables would have to be answered for each pair of subjects, and it is refused, naming both. One that names none of them is a condition on the store as a whole, and it holds or fails alike for every subject the rule concludes about. **It holds for a subject the set never mentions.** A function with no callees at all satisfies "none of its callees is tainted" — vacuously, and that is the intended reading. This is why a `none(...)` is enough on its own to say which subjects a rule applies to (§14.5): the subjects it concludes about are precisely the ones no inner subject reaches, so they can be reached by nothing else. **What it records.** The rule is answered in two stages, and the intermediate is an ordinary derived field on the subject the quantifier is answered for — the outer subject variable its inner premises name, or the subject the rule concludes about when they name none. It is named after the RULE (`__none`, numbered from 1 in the order the `none(...)` premises are written): a rule `is_safe` concluding `f.safe` records `f.is_safe__none1`, and a rule `open_req` over requests and users as above records `u.open_req__none1` on each user. So two rules concluding one field never share one. A rule compiled in a module that records it on a kind the module does not declare puts the module's name first, since two modules may each name a rule alike: `is_safe` in a module `audit`, over a `Fn` no module declares, records `f.audit__is_safe__none1`. It is visible wherever any derived field is — `list`, `current`, `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` exactly for the subjects the `none(...)` excludes. Nothing about it is hidden, and reading it is a supported way to see which subjects the quantifier found. The first stage is an auxiliary **rule**, named after the rule as the field is: a rule `myrule` whose first `none(...)` premise needs one defines `myrule__none1`. That is the name a DIAGNOSTIC can print — a `none(...)` whose inner premises do not range-restrict is refused with "could not define rule `myrule__none1` — it does not say which subjects it applies to" (§14.5). A reader meeting that name is looking at the auxiliary rule for their own `myrule`, which records the field of that name. A `none(...)` whose inner premises never mention the name it binds asks nothing about that name's subjects, and is refused naming `myrule` itself. - A **source** test (`source(r.temp) == "lab"`) holds for a value whose source is the one named — the text a write gave as its source (§7.3), quoted, so `source(r.temp) == "field crew"` names a source with a space in it. It is about where a value came from, not what it is: a field three sources disagree on holds `source(r.temp) == "lab"` through the value the lab recorded, and every other premise on `r.temp`, and a copy of it in the conclusion, reads that same value. `source(r.temp) != "lab"` holds through a value some other source recorded. It does not say the lab did not record the value: a value the lab and a second source both recorded holds `!=` through the second source's record of it. **A value a rule concluded has no source a person gave it**, so it satisfies no source test with either operator. `trust(...)` is how a rule asks about those. **A source joined to a subject.** The right side may be a subject the `for` binds: `source(r.temp) == s` holds when the value's source is that subject's name, which relates a value to what the store says about the one who recorded it: ```python @rule def vetted(): for r in Reading, s in Station: if source(r.temp) == s and s.accredited == True: r.vetted = r.temp ``` A reading recorded by a station the store holds as accredited is vetted, and forgetting the accreditation withdraws the conclusion. With no other premise on `s`, `for s in Station` still means a Station: the join holds only for a source the store holds a `Station` subject by that name for. Only `==` joins; `source(r.temp) != s` is refused. - A **trust** test (`trust(s.label) == "asserted"`) holds for a value a person recorded, and `trust(s.label) == "derived"` for one a rule concluded — the two words `.trust` reads back (§5.2), spelled in English on every surface. `!=` selects the other. A field holds recorded and concluded values together only where a rule yields to a recorded value (§14.3), and that is where the test tells the two apart — the defaults from the values recorded over them: ```python @rule def label_default(): for x in Sensor: if not_asserted(x.label): x.label = x.name @rule def defaulted(): for x in Sensor: if trust(x.label) == "derived": x.defaulted = True ``` Both tests take one field of a bound subject; a relation (`Reach.x.y`) is refused. Neither stands in a constraint (§14.10), whose premises are the comparisons and absences a read can answer. ### 14.3 The conclusion ``` conclusion := cell '=' ( literal | '-' ( INT | REAL ) | fold | copy | text ) # o.review = True, r.adjustment = -5, # r.rate = 0.75, c.order_count = count(o), # d.owner_name = p.name, # d.line = f"{d.title} cites {d.cites}" | 'assert' '(' cell ')' # assert(p.chip) — known to be absent cell := IDENT '.' IDENT # a subject and one of its fields | IDENT '.' IDENT '.' IDENT # a kind, a subject, a field copy := IDENT '.' IDENT # p.name — a field of a bound subject | IDENT '.' IDENT '.' IDENT # Dist.x.y — a relation's value for a pair text := # f"{p.name} ({d.room})" fold := fold-name '(' IDENT ')' # count(o) — fold the subjects | fold-name '(' IDENT '.' IDENT ')' # sum(o.amount) — fold one field fold-name := # count, sum, avg, argmax, … ``` Whenever the condition holds, the engine records the named field of the named subject as the given literal, which may be **negative** (`r.adjustment = -5`), need not be a whole number (`r.rate = 0.75`), and may be written in scientific notation (`s.avogadro = scientific("6.02e23")`), as a complex number (`s.gain = 1+2j`) or as a blob (`d.marker = blob("beef")`). The field then holds a value of that kind, and reads back as that kind. As with a computed field, the customer never writes that field directly: a write there refuses exactly as it does at a computed field (§13.7), and its reason names the rule and the fields its condition reads. Where the conclusion covers every subject of a field the condition also reads, as `p.email = q.email` does over one class, no value can be written to that field for the rule to read. The reason says so instead of naming that field as one to write: give the conclusion a field of its own, or let the rule yield. The one exception is a rule that **yields** — one whose condition includes `not_asserted(...)` on the very field it concludes (§14.2). That rule states that a recorded value wins, so recording one there is accepted, and the rule stops concluding for that subject. A relation conclusion (`Rel.x.y = …`) concludes at every field of its kind, and yields at the one its `not_asserted(x.f)` names: a value may be recorded at `f`, and every other field stays the rule's. A rule is refused on the same terms as a computed field: installing a rule whose conclusion would land on a field that already holds recorded values refuses, naming them, unless the rule yields. Forget those values, or conclude at another field; the refusal changes nothing recorded, and `forget` there is never refused. A cell can be named in two lengths. The **two-segment** form, `o.review`, names a field of a subject the `for` clause bound, and is what most rules use. The **three-segment** form is the same `Kind.subject.field` path used everywhere else in the language — `remember(Order.o1.total, …)` — except that the last two segments may be **bound names** rather than literals. That is what lets a rule record a fact about a **pair**: ``` @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 rule records every direct edge; the second joins a known reach to a further edge and records the longer one. Because the second reads the same relation it writes, it applies to its own results and settles on **everything the graph connects** — not on a fixed number of steps. Reading a relation with a bound name in the field position asks for **every** cell of that subject, so `Reach.x.y` in a premise finds each `y` that `x` reaches rather than requiring one to be known in advance. **A cycle needs nothing further.** A subject that reaches itself is in one, so the query is the relation read with the same name twice: ``` @rule def in_cycle(): for x in Fn: if Reach.x.x == True: x.cyclic = True ``` A name used twice in one cell **agrees with itself** rather than standing for two things: `Reach.x.x` holds only where the subject and the field are the same name, which is what makes this a cycle test and not a list of everything reachable. Every name a conclusion uses is one the `for` clause binds, as a Python name is bound before it is used. A name the `for` does not bind is refused, and the refusal names it. A join or a relation premise binds no name: it **ranges** one, deciding which subjects the name stands for (§14.5). A relation premise ranges both of its names, so a name it reads in the field position needs no comparison of its own, and may be a conclusion's subject as well as its field. This rule records a relation backwards: ``` # reached-by.dpy — a relation read backwards: what reaches each function. @rule def reached_by(): for x in Fn, y in Fn: if Reach.x.y == True: Back.y.x = True def show_reached_by(): remember(Reach.main.parse, True, "graph") b = current(Back.parse.main) print("parse is reached from main: " + str(b.value)) ``` ``` OK script show_reached_by(): 1 claim written, 1 print remember Reach.main.parse = True print parse is reached from main: True ``` As §14.1 says, the premises and not the kind say which subjects a name ranges over, so neither `main` nor `parse` needs to be recorded as an `Fn`. **A conclusion may copy a value the rule read.** `d.owner_name = p.name` records, for each device, the name of the person its `owner` names. The right side is a field of a subject the `for` clause binds, or the value a relation holds for a pair of them, written as a premise writes it, and the value recorded is the value read there, of the same kind: a copied datetime is a datetime, a copied blob is a blob, a copied `5` is the number and not the text. ``` # owner-name.dpy — a device records its owner's name, read across the join. @rule def owner_name(): for d in Device, p in Person: if d.owner == p: d.owner_name = p.name def show_owner(): remember(Device.d1.owner, "ann", "inventory") remember(Person.ann.name, "Ann Lee", "hr") n = current(Device.d1.owner_name) print("owner: " + str(n.value)) ``` ``` OK script show_owner(): 2 claims written, 1 print remember Device.d1.owner = "ann" remember Person.ann.name = "Ann Lee" print owner: Ann Lee ``` - **A copy reads the field, so the field's presence is a premise.** A device whose owner has no name recorded is not concluded about, the reading a field comparison has (§14.2). The copy also ranges the subject it reads (§14.5), so a copy that yields needs no comparison: `if not_asserted(s.label): s.label = s.name` labels every sensor that has a name, and a label someone records wins. - **A copy records every value the field holds.** A field that two sources disagree on holds two values, and the rule records one conclusion from each, so the disagreement travels into the copied field (§14.4). A premise on the copied field and the copy read one value of it (§14.2): `if r.t > 30: r.hot = r.t` copies only the values above `30`. - **A copied value is valid while the value it was read from is.** A value recorded with a validity window is copied with that window, narrowed to the overlap with the other premises' windows (§14.4). - **A relation's value is copied as a field's is.** `r.dist = Dist.x.y` copies the value `Dist` holds for the pair `x`, `y`, and `Km.x.y = Dist.x.y` carries it into another relation, pair for pair. Both names must be bound, as in a relation premise (§14.2), so a path naming a specific subject (`Order.o1.total`) is not copied. Everything above holds for it: the pair's presence is a premise, the copy ranges both names, every value the pair holds is copied, and a premise on the same pair reads the value the copy does — `if Dist.x.y < 6: r.near = Dist.x.y` copies only the distances under `6`. - **A conclusion cannot read a field its rule tests with `known_absent`.** A recorded absence has no value, and a premise and the conclusion on one field read one value (§14.2), so a copy (`p.copied = p.chip`), a fold (`count(p.chip)`) or a text written from it (`f"{p.chip}"`) has nothing to carry. The rule is refused when you define it. To conclude that a field is known to be absent, write `assert(p.chip)` (below). - **A conclusion cannot copy what it concludes.** A copy of the concluded field on the same subject (`x.a = x.a`), or of the concluded pair (`Km.x.y = Km.x.y`), would be its own only source, so it could never record anything. The same field of **another** subject is how a value travels along a link: ``` @rule def same_name(): for x in Member, y in Member: if x.same_as == y and not_asserted(x.name): x.name = y.name ``` A member linked to one whose name is recorded takes that name; a member linked to *that* member takes it too, however long the chain; and a member whose own name is recorded keeps it. - **Copying makes no new values.** Every copy records a value the store already holds, so rules may copy into each other without threatening termination (§14.6). A copy that feeds a fold's own group is that fold's cycle, and is refused as one (§14.5). **A conclusion may write a text from values the rule read.** `d.line = f"{d.title} cites Version {d.cites}."` records, for each document the condition holds for, one sentence written from two of its fields. The right side is an f-string (§3.5.10) whose fields are each what a copy's right side is: a field of a subject the `for` clause binds, or the value a relation holds for a pair of them. The text recorded is the one a program's f-string prints over the same values, and it is a conclusion like any other: it is rewritten when a field it reads is revised, it goes when the condition stops holding or a field it reads does, `why` explains it by the claims it was written from, and a write at the field refuses as it does at any concluded field. ``` # cites-line.dpy — a sentence about each document, written from two of its fields. @rule def cites_line(): for d in Doc: if d.status == "published": d.line = f"{d.title} cites Version {d.cites}." def show_line(): remember(Doc.security.status, "published", "web") remember(Doc.security.title, "Level 2 Security", "web") remember(Doc.security.cites, "1.9", "web") a = current(Doc.security.line) print(str(a.value)) update(Doc.security.cites, "1.10", "web") b = current(Doc.security.line) print(str(b.value)) ``` ``` OK script show_line(): 4 claims written, 2 prints remember Doc.security.status = "published" remember Doc.security.title = "Level 2 Security" remember Doc.security.cites = "1.9" print Level 2 Security cites Version 1.9. update Doc.security.cites = "1.10" print Level 2 Security cites Version 1.10. ``` - **A written text is a `string`, whatever it reads as.** `n.shown = f"{n.count}"` over a count of `4` records the text `"4"`, where the copy `n.shown = n.count` records the number. An f-string with no field is a string literal, and is the literal conclusion above. - **Each field is read as a copy reads its one.** Its presence is a premise, so a document with no version recorded has no sentence; the text ranges each subject it reads a field of (§14.5); a field that two sources disagree on gives one text for each value it holds; a premise on a field and the text read one value of it (§14.2); and the text is valid while every value it was written from is. - **A field holds a `string` or an `int`.** Those are written as a program writes them, a text as itself and a whole number as its digits. Where a field holds a value of another type — a `real`, a `bool`, a `datetime` — no text is drawn for that subject, and `why_not` on the concluded field names the field and the value it holds. - **A field is written plain.** `{d.title}` and `{Dist.x.y}` are the whole of what a field may be: a conversion (`{d.title!r}`), a format (`{d.count:>5}`), a debug field (`{d.title=}`), a subject alone (`{d}`) and any other expression (`{d.count + 1}`) are each refused when you define the rule. A conclusion computes nothing, so a value that needs working out is concluded into a field of its own, and that field is named. - **A text has a ceiling of 65,536 bytes.** A text that would be longer is not drawn, for that subject alone, and `why_not` says so. - **A text cannot be written from itself.** The concluded field on the same subject (`x.a = f"{x.a}!"`) is refused where it is written, and a rule that closes a cycle through a written text is refused as §14.5 says. **A conclusion may record that a field is known to be absent.** Written in the conclusion's place, `assert(p.chip)` records, for each subject the condition holds for, what `assert(K.s.a, src)` records when a program writes it (§7.1): that the field is known to be absent. It takes no source, because the rule is the source of what it concludes, and a second argument is refused. The three-segment form records it about a pair, as `assert(Gap.x.y)`. ```python @rule def strays_have_no_chip(): for p in Pet: if p.status == "stray": assert(p.chip) ``` A `known_absent(...)` premise holds over what this records, `absent(...)` does not, and `not_asserted(...)` does, since no person recorded it (§14.2). It is a conclusion like the others: it goes when the condition stops holding, a write at the field refuses as it does at any concluded field, and a value another rule concludes at the same field is held beside it, so the field is in disagreement (§7.6). **A conclusion may fold the subjects it ranges over instead of naming a literal.** `c.order_count = count(o)` records, for each customer, how many orders met the condition *with that customer*. 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) @rule def regular(): for c in Customer: if c.order_count > 3: c.regular = True ``` A customer with four orders gets `4`; a customer with one gets `1`. That per-subject answer is the difference from an aggregate **query** (§7.1.1), which folds a whole attribute across every subject of a kind and yields a single number: `count(Order.customer)` is how many orders exist, and `count(o)` here is how many are *this customer's*. Two shapes, by what is folded: | Written | Folded | |---------|--------| | `count(o)` | the bound subjects themselves — a bare subject variable | | `sum(o.amount)` | one field of those subjects | **Which folds may appear.** The **single-operand** folds of §7.1.1 — every family there except *ranked* and *two-attribute*. `percentile(o.amount, 90)` and `slope(o.x, o.y)` need an argument a conclusion has nowhere to put, and are refused here rather than admitted half-formed. The rest keep their §7.1.1 meanings exactly, the identity and positional folds included: `argmax(o.amount)` records the **subject** holding this group's largest amount, and `distinct(o.region)` records how many distinct regions this group covers. **A subject missing the folded field contributes nothing to a fold over that field, and still counts toward `count(o)`.** Four orders of which three carry an amount give `count(o)` of `4` and `sum(o.amount)` over the three. Naming a field makes its presence a premise — the same reading a field comparison has (§14.2). **A fold ranges over the premises' MATCHES, and a location holding more than one value in force matches once per value.** §7.6 already states the general rule — a standing rule reads every value at a location in disagreement and derives from each — and a fold is where it becomes visible as arithmetic. Take two orders, and let a second source also record that the first belongs to this customer. That order now matches twice, and `count(o)` answers `3` for two orders. **Each fold follows the claims of the field IT reads**, so the two shapes move independently, and the difference is worth knowing before reading a number: | what holds two values in force | `count(o)` | `sum(o.amount)` | |---|---|---| | nothing | `2` | `30` | | the joined field (`o.customer`) | `3` | `30` — unchanged | | the folded field (`o.amount`) too | `3` | `50` — the amount is added twice | `count(o)` folds the join's matches, so a second claim on the joined field raises it. `sum(o.amount)` folds the amount's own claims and does not see the join's extra match at all; only a second claim on the amount adds it again. **A premise on the folded field decides which of its values are folded.** Premises and the fold that name one field read one value of it (§14.2), so `if o.customer == c and o.amount > 10: c.big = sum(o.amount)` folds only the amounts above `10`. An order whose amount is `5` from one source and `15` from another contributes `15`, not `20`. **For the number of distinct subjects, fold `distinct(o)`**, which answers `2` over the same data and is what `subjects(Order.customer, c)` counts in program position. Nothing here is special to folds — an ordinary conclusion is derived once per matching value too — but a literal conclusion records the same value twice and looks unchanged, while a count does not. **The folded name must be bound by the `for` clause**, like every other name a rule uses. Folding a name the rule does not range over is refused, and the refusal names that name. **A fold that declines when its rule is defined refuses the definition, and nothing else.** Defining a rule whose fold declines over the values its field already holds (§14.9 — a `string` where the rule sums, say) is refused, and the refusal says the fold cannot read a value there: correct or remove that value, or fold with one that takes it, such as `count`. The refusal is that rule's alone. A compile that defines only rules whose folds take what is recorded succeeds beside a rule that declines, and a program that writes such a value is not refused either: the declining rule keeps the last value it concluded, read with `maintained` set to `False` (§5.2). The third rule above is the ordinary case that makes the first two worth having: a derived field is a premise like any other (§14.4), so "a customer with more than three orders is a regular" is one comparison against a field nobody wrote. A fold may not, directly or through other rules, feed a premise that the same fold depends on — see §14.5. ### 14.4 What a rule does Defining a rule takes effect immediately and continuously: - A rule's conclusion is **in force exactly while its condition holds over undisputed data** — evaluated over the data present at definition time and kept current as data is written or removed (including when a value is first recorded for a field an `absent(...)` premise required to be absent). A conclusion in force carries a `derived` provenance (§7.1) in place of a source. - When a conclusion's condition ceases to hold, the conclusion ceases to be in force; when it comes to hold, the conclusion is in force. A rule therefore states an **invariant** over the data, not a one-time action. - **A premise in disagreement carries the disagreement into the conclusion** (§7.6). While a premise location holds claims from different sources with different values, a rule reading it derives once from each value that satisfies its condition, and every such conclusion is in force. This holds whether the disagreement arose before or after the conclusion was first derived: a conclusion already standing is not withdrawn when its premise comes into dispute, and the contesting value's conclusion joins it. Conclusions collapse back to one when the disagreement clears — where the disagreeing claims were written, by `update`ing or `forget`ting one of them; where they are themselves conclusions, neither reaches, since a conclusion `forget` withdraws is recorded again while its condition holds and an `update` adds a further claim beside the two, so that disagreement clears by changing the rules that produce it. The outcome depends on the data, never on the order the claims arrived: recording the disagreement first and the rule second reaches the same conclusions as the reverse. A derived field reads back exactly like any other field (`current`/`get`, §7.1); its value reflects the rules currently in force. **A conclusion is valid while its premises are.** A premise may carry a validity window (§7.1), and a conclusion drawn from windowed premises is valid over the time they are **all** valid together — the overlap of their windows, and no wider. Nothing else about the rule changes: the conclusion is in force exactly while its condition holds, and its window says which times that condition is being asked about. ``` # window-overlap.dpy — a conclusion drawn from two windowed premises is valid # over the time both premises are valid, and not outside it. @rule def flagged(): for e in Emp: if e.status == "active" and e.dept == "eng": e.flagged = True def overlap_report(): remember(Emp.e1.status, "active", "hr", datetime("2020-01-01T00:00:00Z"), datetime("2022-12-31T00:00:00Z")) remember(Emp.e1.dept, "eng", "hr", datetime("2021-01-01T00:00:00Z"), datetime("2023-12-31T00:00:00Z")) inside = get(Emp.e1.flagged, datetime("2021-06-01T00:00:00Z")) print("inside the overlap: " + str(len(inside))) after = get(Emp.e1.flagged, datetime("2023-06-01T00:00:00Z")) print("after it: " + str(len(after))) ``` ``` OK script overlap_report(): 2 claims written, 2 prints remember Emp.e1.status = "active" valid_from=datetime("2020-01-01T00:00:00Z") valid_to=datetime("2022-12-31T00:00:00Z") remember Emp.e1.dept = "eng" valid_from=datetime("2021-01-01T00:00:00Z") valid_to=datetime("2023-12-31T00:00:00Z") print inside the overlap: 1 print after it: 0 ``` The two premises overlap from 2021-01-01 to 2022-12-31, so that is when the conclusion is valid. An as-of read (§7.1) inside the overlap finds it and one outside does not, which is why the second read above answers empty although `dept` is still valid then. A bare read answers about the present, so once the overlap has passed the conclusion is **absent from the present** — the correct answer, and not a rule that stopped working. A premise carrying **no** window is valid at all times and therefore narrows nothing: a conclusion drawn from one windowed and one unwindowed premise takes the windowed one's bounds. A premise with one open bound narrows only the side it names. **Premises that never overlap conclude nothing.** Where the windows have no time in common there is no moment at which the condition holds, so the rule records no conclusion at all — at no time, the present included. That is not an error and is not reported as one: the rule is well-formed, it applied, and the data gave it nothing to conclude. `why_not` (§7.1) on the cell names the premise that is not in force at the time asked about, which is what tells this apart from a rule that never applied and from a cell no rule concludes. **A window carries through a chain.** A conclusion is an ordinary premise to any rule that reads it, and it carries its own window when it is read that way. A rule concluding from a conclusion is valid over the same overlap the first one was, and a chain of rules narrows to the time every fact along it is valid. ### 14.5 Well-formed rules Two things make a rule usable; a rule that lacks either is rejected when you define it, with a message that names the rule. - **A rule must say which subjects it applies to** — every subject it binds, not just one of them. A subject the `for` binds and nothing else in the rule mentions is not used, and the message says so: remove it, or relate it to the others with a join. A subject only the conclusion mentions has no premise to say which subjects it concludes about, unless the conclusion copies from it (below). A subject whose only mention is an absence test never says *which* subjects, so the rule has nothing to work over (`for o in Order … if absent(o.shipment): …` — which orders?). The same applies to a `not_asserted(...)`, for the same reason and not a second one: it names one cell and describes subjects without saying where to find them. Over a pair (`absent(Gap.x.y)`) the two forms range neither name, for that reason. In the defeasible rule of §14.2 the `b.species == "avian"` premise is what ranges `b`, and a default worth stating almost always has such a premise already — the population it is a default FOR. These premises range a subject: a **comparison**, a **join** (either side, `==` only — a `!=` join says which subjects are excluded, which is the same non-range an absence has), a **relation** premise, which ranges both of its names, a **known absence** (over a pair, both of its names), a **nonexistence**, and a **source** or **trust** test, which reads the field it names (§14.2). A source joined to a subject (`source(r.temp) == s`) ranges that subject too, over the subjects the store holds of its kind. Give each subject at least one of those. A conclusion that copies a field (§14.3) ranges the subject it copies from as well: it reads that field, so the set is the subjects that have it. A copy of a relation ranges both of its names, as a relation premise does. A conclusion that writes a text ranges each subject it reads a field of, for the same reason. A nonexistence ranges where an absence does not, and the difference is not arbitrary even though both are negative: an absence names one cell and nothing else, so it describes subjects without saying where to find them. A `none(...)` carries a whole inner rule, and the subjects it concludes about are exactly the ones that inner rule does not reach — a set the engine can enumerate, because it is the kind's own subjects minus the ones the inner premises matched. So the range is not an extra requirement placed on the author; it falls out of what the premise means. This is what lets *"a function is safe when none of its callees is tainted"* be written as it reads, with no unrelated anchor field invented to give the rule something to stand on. - **A rule's outcome must settle.** A rule — alone or together with others — must not chase its own tail: if recording its conclusion would remove the very condition that produced it, there is no answer to settle on, and the rule is rejected. You may build rules on stored data and on one another freely; what is ruled out is a set whose outcome cannot settle, which is a set that cancels itself and the two below. A relation conclusion (`Rel.x.y = …`, or `assert(Rel.x.y)`) can record any field of its kind, since the field is whatever `y` names, so it counts as concluding every one of them: a rule that tests `absent(r.f)` on a subject of that kind and concludes a relation into it is rejected for this reason, whichever field `f` is. Conclude the pair into a kind of its own, whose fields the rule does not test, and nothing it records can undo the absence. An absence over a pair (`absent(Gap.x.y)`) is the same fact from the other side: it tests whichever field of `Gap` `y` names, so a rule that tests it and concludes any field of `Gap`, of a subject of that kind or of a pair, is rejected for this reason. `not_asserted(Gap.x.y)` reads only what people wrote, so it may stand beside such a conclusion. **A fold (§14.3) settles on the same terms, for a second reason.** A fold reads a whole group before it can answer, so a fold whose own result feeds — directly or through other rules — a premise that fold depends on is asking for a number that changes as soon as it is written. That set is rejected at definition too. Counting from data other rules derive is fine; what is ruled out is a count that is a member of what it counts. **A computed field (§13.7) that calculates settles on the same terms, for a third reason.** A computed field whose initializer applies an operator (`total: int = price * qty`) works its value out from its inputs, so a rule whose conclusion feeds — directly or through other rules — an input of such a field, while the rule depends on that field's value, would make the value depend on itself. That set is rejected at definition too. A rule may read such a field and may conclude into its inputs; what is ruled out is doing both around one cycle. A field that copies one other field or compares (a `bool` field) may sit on a cycle with rules, since its value settles (§13.7). **A text a rule writes (§14.3) settles on the same terms, for a fourth reason.** A conclusion that writes a text makes a new value from the ones it reads, so a text that fed, directly or through other rules, a field it is written from would be written from itself, and each writing would be longer than the last. A rule that closes such a cycle is rejected at definition, whichever rule of the cycle it is, and the message names the text. Other rules may read a written text freely; what is ruled out is a text that is one of its own sources. ### 14.6 Termination Standing rules preserve the termination guarantee of §9.5. A rule ranges over finitely many stored subjects; its premises are tested, not iterated; and §14.5 rules out the four constructions under which the outcome could fail to settle, a set that cancels itself, a fold that counts its own result, a computed field that calculates from its own value and a text written from itself. Every set of rules therefore reaches a fixed set of derived facts in finite time. ### 14.7 `list(target = rules)` ``` list(target = "rules") ``` Lists the **names** of the standing rules you wrote with `@rule`, one per line — the rule-authoring counterpart of `list(target = scripts)` ([MCP Reference §9.2](/ref/dke/mcp/#scripts-address-by-module.script)). A rule with a kind (§14.1) is listed with the kind after its name, `heat.warm (causal)`. Rule names come back in **ascending name order**, the same guarantee §5.1 gives the enumerating reads a program can call. A rule a module defines is listed under its `module.rule` surface name and sorts under that. Only the rule **names**, with their kinds, are returned; the listing carries no premises or conclusions. To see what a rule does, keep your own source — the service stores no rule source. `list(target = rules)` is **wire-only** and its response is rendered text, not a typed value; there is no expression-position form of it inside a program (unlike the script listing, which a program also reads, as `list_scripts()`, a `string_list`). The listing is **authored-only**. A computed field (§13.7) is a standing rule — it fires, it derives, and it can stop concluding — and it is not listed here, because it has no name you gave it: its address is the field it fills. So an empty listing means you have written no `@rule`, **not** that nothing is reasoning over your store. A store declaring `area: int = w * h` and no `@rule` lists nothing here and reports `rules=1` from `checkup` (§14.9), which counts every standing rule in force. Read the two together: `list(target = rules)` answers *what did I name*, `checkup` answers *what is standing*, and the difference between them is the computed fields. ### 14.8 `forget_rule(name)` ``` forget_rule(name = ".") ``` Removes **one** standing rule and everything it derived. `name` is the qualified `module.rule` name that `list(target = rules)` (§14.7) prints. The removal is hard, the same as any other removal (§7.1): the rule stops firing and leaves the rule listing, and the facts it concluded are reclaimed. Facts a removed rule concluded do **not** all disappear. A conclusion that another standing rule still supports is re-derived and stays — only the support this rule contributed is withdrawn. So removing one of two rules that agree leaves their shared conclusion standing, on the surviving rule. Removing a rule does not touch the facts it **read**. Premises are your data; only conclusions are the rule's. An address with no rule behind it removes nothing, and the call succeeds, saying the rule is not stored. `forget_rule` is **wire-only** — there is no expression-position form inside a program — and it needs a `read-write-delete` key, as every removal does. To bring a removed rule back, `compile` its module again: the rule returns and re-derives its conclusions over the facts that are still there. This holds of every rule in a store, and a removed rule stays removed until then. ### 14.9 `checkup` ``` checkup ``` Reports the store against the standing rules and constraints in force over it. It takes no arguments, changes nothing, and needs only a read key. It exists because a rule that never fired and a rule that fired and found nothing produce the same empty result. Where the reasoning came from a module the reader did not write, nothing else in this specification distinguishes the two: §14.7 gives rule names and no premises, and the data verbs (§7.1) describe the store without reference to the reasoning over it. The response is a counts line followed by findings, one per line, in a closed set of forms: | Form | Holds when | |---|---| | `unmet . no-such-kind` | some standing rule's or constraint's premise reads that cell and the store holds no kind of that name | | `unmet . no-values` | the kind is present and no claim stands at that field for any subject | | `unmet . not-in-force` | the kind is present and a claim stands at that field, and every such claim carries a validity window (§7.1) that does not cover the time asked about | | `unread ` | the kind holds claims and no standing rule's or constraint's premise reads it | | `near ~ ` | `A` is unread, `B` is the kind of an unmet `no-such-kind` cell, and their names differ only slightly | | `violated …` | a standing constraint (§14.10) does not hold; `` is how many subjects breach it, and at most **five** of them are listed | | `counts rules= unfirable= unreadable= declined= kinds= unread= constraints= unevaluable= violated=` | always; `rules` counts every standing rule in force, the computed fields a class declares (§13.7) included — see below, and note `list(target = rules)` (§14.7) names only the `@rule` ones; `unfirable` counts rules with at least one unmet premise, `unreadable` counts rules this version of the service cannot read (see below), `declined` counts rules whose fold cannot take the values it reads (see below), `unevaluable` counts **constraints** with at least one unmet premise — an invariant that could not be tested, which `violated=0` alone would report as one that holds — and `violated` counts constraints that do not hold | Kinds are named in the addressable form (`.` for a kind a module declares), which is the form §7.1's verbs take. Findings name **cells and never rules**. A cell is part of a module's interface — `info_module` ([MCP Reference §9.4](/ref/dke/mcp/#modules-address-by-bare-module-name)) already enumerates the classes a module declares — while which premise belongs to which rule is part of its body, which is not readable. Rules are therefore counted, not named. **A premise that asks for an absence.** `absent(...)`, `not_asserted(...)` and `none(...)` (§14.2) hold where a field has no value, or none a person recorded, so an empty field meets them. The `unmet` forms above are about the premises that need a value. A premise that asks for an absence raises no `unmet` finding and does not count its rule `unfirable`. A field that holds what it excludes keeps the rule from concluding for the subjects that hold it, and for no other, while `unmet` and `unfirable` say what no subject can meet. So that is not a finding here either, and `why_not` (§5.2) says it of one subject. The premise still reads its kind, so a kind read only by such a premise is not `unread`. **The rule a `none(...)` is answered through.** A `none(...)` is answered by an auxiliary rule (§14.2) whose premises are the ones inside its parentheses. It stands over the store as any rule does, so `rules` counts it, and it asks for an absence as the `none(...)` does: its premises raise no `unmet` finding and it is never counted `unfirable`. That it cannot fire is the `none(...)` holding for every subject, so a finding naming the cells inside the parentheses would ask for exactly what the `none(...)` excludes, and recording it would withdraw the conclusions the `none(...)` let stand. Those cells still read their kind, so a kind read only inside a `none(...)` is not `unread`, and a rule that reads one of them as a value raises it as any rule does. **Which rules are counted.** Every standing rule in force over the store: the rules you wrote with `@rule` **and** the computed fields your classes declare (§13.7). A computed field is a rule you did not name, and it is counted here 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)` (§14.7) is authored-only, so `rules=` is at least as large as that listing and the difference between them is exactly the computed fields. The two verbs answer different questions and are meant to be read together. So the mixed store below is not a contradiction, and both readings are right. Compiled as a module named `shapes` ([MCP Reference §9.1](/ref/dke/mcp/#language)), so the names below carry its prefix: ``` class Box: w: int h: int area: int = w * h # a computed field — counted, never listed big: bool @rule def bigbox(): # an authored rule — counted AND listed for b in Box: if b.area > 100: b.big = True ``` Over an empty store, `list(target = rules)` names one rule, `shapes.bigbox`, while `checkup` reports `rules=2 unfirable=2` and names three unmet cells — `shapes.Box.w` and `shapes.Box.h`, the computed field's premises, and `shapes.Box.area`, the authored rule's. Both rules are represented and neither is named: a cell is what you can act on. Write a `w` and an `h` and the same store reports `rules=2 unfirable=0`, both rules having concluded. A store declaring the class and no `@rule` at all reads `rules: none` from one verb and `rules=1` from the other, in the same second. Appendix C.30 is this class as a runnable program. **Unreadable rules.** A rule is stored when its module is compiled, in a form that the version of the service which compiled it reads. A rule stored by another version may be one this version cannot read. Such a rule still stands, and `rules` counts it, but it never fires, whatever the store holds. `unreadable` counts these rules and nothing else reports them. The report does not read their premises, so none of their cells is called unmet and none of them is counted `unfirable`: a finding about a rule that cannot conclude would ask for data that changes nothing. A store whose rules were all compiled by this version reports `unreadable=0`. **Declined rules.** A conclusion that folds (§14.3) takes the values its folded field holds under §7.1.1's rule: a fold that compares by value declines while that field holds a value it cannot take — a `string` where the rule sums, say — and `count` takes any value. While one fold of a rule declines, the rule concludes nothing, at **any** of its conclusions, until those values change: a `count` written beside the declining `sum` in the same rule stops with it. `declined` counts these rules, and `rules` still counts them. Their premises are read as any rule's are, so a declined rule with an unmet premise is counted `unfirable` too, and filling that cell leaves it declined. The rule is not named, for the reason above; `why_not` and `what_needs` on any of its conclusions name it. A computed `bool` comparing values that cannot be ranked against each other is **not** counted here: it concludes for one subject and not the next, which is a fact about that subject's value, and `why_not` reports it for that subject. Nor is a computed field whose arithmetic has no answer for one subject's values — a divisor of zero, say — for the same reason. A `violated` finding names the **constraint and the offending subjects, and not the cell** — the mirror of that rule rather than an exception to it. An `unmet` finding is about the store's SHAPE, so a cell is the right noun and naming the rule would map a premise to a body. A `violated` finding is about one named invariant the author declared, so the constraint is the right noun, and naming the cell would map a condition to that constraint's body in exactly the way the paragraph above forbids. The subjects are what make it actionable: knowing that `stock_never_negative` broke is worth little without knowing which products broke it, and a subject id is data the reader already owns. The COUNT is exact and the list is capped at five, so a constraint breached by a whole kind reports the scale of the breach without putting the store's subject list in a report meant to be read. `checkup` is **wire-only**: there is no expression-position form inside a program, and using the name in a program body is an ordinary identifier. An empty store yields the counts line alone. Listing order is contract on both axes. Findings are **grouped by form**, in this order — the `counts` line, then every `unmet`, then `unread`, then `near`, then `violated` — and **within each group in ascending name order**. A form with no findings contributes no lines rather than a heading. --- ### 14.10 `@constraint` A **constraint** watches a condition and reports. It concludes nothing, writes nothing, and can never fire: ``` @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` clauses, and no head. `violated` is a statement, not a value — it marks the block as a constraint body and is a reserved word only in that position. **A breach does not become data.** The alternative without this construct is a rule concluding `p.invalid = True`, which records facts ABOUT the store INSIDE the store, and then feeds them to other rules as though they were observations. A constraint has no conclusion to record, so there is nothing for a later rule to read. That is the whole difference, and it is why a constraint is not simply a rule whose head you ignore. A constraint is evaluated by `checkup` (§14.9), which reports one `violated` finding per constraint that does not hold. It is never evaluated 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 that says so. ``` @constraint def every_order_has_a_customer(): for o in Order: if absent(o.customer): violated ``` **Premises are a subset of the §14.2 grammar**, and the subset is the whole of what a constraint can be asked. Two shapes are admitted: | premise | example | |---|---| | a comparison against a literal | `p.stock < 0`, `p.tier == "gold"` | | an absence | `absent(o.customer)` | Several joined by `and` are allowed, and hold when a subject satisfies all of them. Premises on one field hold through one value of it, as a rule's do (§14.2): `p.stock > 0 and p.stock < 10` is violated by a product holding a stock between the two, not by one holding `20` and `-5`. 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. Anything else — a premise relating two subjects, a quantifier, a premise that selects on how a value came to be recorded — is refused when the constraint is defined, naming the two shapes that are accepted. ``` constraint-decl := '@constraint' NEWLINE 'def' IDENT '(' ')' ':' NEWLINE INDENT 'for' subject-binding ':' NEWLINE INDENT 'if' constraint-premise ( 'and' constraint-premise )* ':' NEWLINE INDENT 'violated' NEWLINE DEDENT DEDENT DEDENT constraint-premise := IDENT '.' IDENT ( ord-op | eq-op ) literal # a comparison against a literal | IDENT '.' IDENT '.' IDENT eq-op literal # the same, over a relation (§14.2) | 'absent' '(' IDENT '.' IDENT ')' # an absence ``` A constraint also binds **exactly one subject variable**. A rule may relate two kinds (`for c in Customer, o in Order`); a constraint may not. Both restrictions have one cause: a constraint asks only what a read of the store can answer, and the premises above are exactly those a read can express. To check something outside that, conclude it into a field with a `@rule` and write the constraint over that field. The rule does the reasoning, the constraint does the watching, and neither grows the other's job. **Naming.** A constraint shares the unit's one name space with scripts, rules and classes (§4.2), and `list(target = rules)` (§14.7) does not list it — a constraint is not a rule and a caller enumerating rules to reason about derivation would be misled by one. `checkup`'s counts line reports how many stand, and the qualified name it prints is also the address `forget_constraint` (§14.11) takes. **Termination** is not a question a constraint raises. It derives nothing, so it cannot feed itself, and its evaluation is a bounded pass over the subjects of one kind. ### 14.11 `forget_constraint(name)` ``` forget_constraint(name = ".") ``` Removes **one** standing constraint. `name` is the qualified `module.constraint` name that `checkup` (§14.9) prints in a `violated` finding and counts on its `constraints=` line. **Nothing else changes**, and that is the whole of why this verb is separate from `forget_rule` (§14.8). A constraint concludes nothing, so there is no conclusion to withdraw and no fact to reclaim. Removing a rule raises a question about the facts it derived; removing a constraint raises none, because it derived none. After the removal the store holds exactly what it held before, and the only difference is that `checkup` no longer reports on that invariant. The data the constraint watched is **untouched — including the subjects that breached it**. A breach was never recorded (§14.10), so there is nothing to clean up: the products with negative stock are still there, and their stock is still negative. Stopping the watch is not fixing the breach, and this verb does not pretend otherwise. An address with no constraint behind it removes nothing, and the call succeeds, saying the constraint is not stored. `forget_constraint` is **wire-only** — there is no expression-position form inside a program — and it needs a `read-write-delete` key, as every removal does. To bring a removed constraint back, `compile` its module again. `forget_module` ([MCP Reference §9.4](/ref/dke/mcp/#modules-address-by-bare-module-name)) removes a constraint too, but with the module's classes, its scripts, its other constraints and the data in its kinds. --- ## 15. Hypothesis A **hypothesis** reads what a value *would be* under supposed facts, without changing anything. `suppose(, , …, )` sets each named field to the value beside it, lets your standing rules (§14) and computed fields (§13.7) run as if those were true, reads the final field, and then discards the suppositions — the store is left exactly as it was. ``` r = suppose(Order.o1.total, 2000, Order.o1.review) ``` A scenario usually needs more than one fact to be true at once. Give as many `, ` pairs as it takes; the field to read is always the last argument, alone. ``` r = suppose(Order.o1.total, 2000, Order.o1.shipment, "sent", Order.o1.review) ``` Read: "if the order totalled 2000 **and** had shipped, would it need review?" A rule requiring both conditions is answered by supposing both at once. Read: "if `Order.o1.total` were 2000, what would `Order.o1.review` be?" If a rule sets `review` when `total > 1000`, `r` is `True` — yet afterward `Order.o1.total` keeps its real value (or stays unset) and no `review` is recorded. Nothing the hypothesis touches persists. ``` hypothesis := 'suppose' '(' ( path-expr ',' literal ',' )+ path-expr ')' ``` - Each **supposition** `, ` sets one field to one literal value. A hypothesis takes **1 to 16** of them. They apply **left to right**, so two suppositions naming the same field leave the later one standing — the same rule an ordinary sequence of writes follows. A value taken from a variable is refused. - The **limit of 16** is a cost bound, not a semantic one: the total cost grows faster than the count does. A scenario written by hand does not approach 16; the limit exists so that a program which would has a compile error naming the number rather than an invisible cliff at run time. - A single hypothesis supposes facts **into place** or supposes them **away** (§15.1), not both. Mixing the two in one hypothesis is not available. - Every ``, supposed or read, is a `K.s.a` cell written as a verb's is (§7): a segment held by a `string` name in scope is that name's value, and an instance's field `s.f` is that instance's cell (§13.3), through any name that holds one. - A `` that is no path, or goes on past one as an expression, is a parse_error at it, in a hypothesis of either kind (§15.1), as a verb's path argument is (§7.1): `r = suppose(Order.o1.total + 1, 2000, Order.o1.review)` is one at column 13, "`suppose` takes a path here". Python reads such a call, so a grammar error in it, later in the call, or below it, is the answer instead (§10.1). - The **read** `` names the field whose value is returned — a plain stored field, or one a rule or computed field derives from the supposition. - The result is a **value** (§5.6), bound to a name and consumed with `match` like a cell read or an aggregate (§7.1.1). Concatenating it into a `print` renders it directly. If the read field has no value under the supposition, `suppose` produces no value — a catchable condition (guard it with a `try` / `except` handler, or read a field a rule is known to derive). - The read field is in one of **three states** under the suppositions, and `suppose` answers each the way `current` (§7.1) answers it over the real store: **one value**, which is the result; **no value**, the catchable condition above; or **competing values** (§7.6), which refuses with `current`'s own reason and teaching hint. It never picks one of the competing values for you. A disagreement that a rule derives only under the suppositions counts, because it is part of the state the hypothesis describes, and there the refusal is itself the answer: under those facts, the field would be in disagreement. `get` reads the full claim set of the real store; there is no hypothetical `get`. A hypothesis never writes: the suppositions and everything derived from them are discarded when `suppose` returns, so reading under a hypothesis has no side effect — safe for preview, comparison, and what-if exploration. It preserves the termination guarantee of §9.5: each supposition is one bounded reasoning pass, the number of them is fixed where the call is written, and there is one read. ### 15.1 Supposing a fact away `suppose_absent(, …, )` asks the opposite question: **what would this field say if those facts did not exist?** It supposes each named field's fact away, lets your standing rules and computed fields run without them, reads the final field, and discards the suppositions. ``` r = suppose_absent(Order.o1.total, Order.o1.review) ``` As with `suppose`, name as many fields as the question needs. The field to read is the last argument, alone — so `suppose_absent(a, b, c)` supposes away `a` and `b`, and reads `c`. ``` r = suppose_absent(Order.o1.total, Order.o1.shipment, Order.o1.review) ``` Read: "if `Order.o1.total` were not recorded, what would `Order.o1.review` be?" If a rule sets `review` from `total`, `r` is **empty** — the conclusion has nothing left to stand on. Afterwards `Order.o1.total` still holds its real value and `review` still holds its derived one; nothing was removed. ``` absence-hypothesis := 'suppose_absent' '(' ( path-expr ',' )+ path-expr ')' ``` - Each **supposition** `` names one field whose fact is supposed away. As with `suppose`, a hypothesis takes 1 to 16 of them, applied left to right, and each field is written as `suppose`'s are. - The **read** `` names the field to read under that absence — a stored field, or one a rule or computed field derives. - The result is an **active claim** (§7.1) that may be **empty**, consumed with `match` exactly like `current`, whose third state it shares: a read field holding competing values refuses rather than returning one of them (§15): ``` 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") ``` This is the one place where the two hypothesis verbs differ in what they hand back, and the difference is the point. `suppose` asks *what would this value be*, so a missing answer is an edge case and arrives as a catchable condition. `suppose_absent` asks *what would survive*, and **"nothing" is the answer it exists to give** — so it arrives as an ordinary empty read rather than as an error you must catch to learn what you asked. **Why the verb exists.** `forget` (§7.1) cannot be undone. `dependents` tells you what *points at* a fact; it does not tell you what the answers *become* without it. `suppose_absent` is the rehearsal: run it over the fields you care about before you remove anything, and see what disappears while it is still there to put back. Like `suppose`, it never writes, and it preserves the same termination guarantee: one bounded reasoning pass per supposition, a count fixed where the call is written, and one read. --- ## Appendix A — Full grammar in one block ``` # §3.4 identifier := XID_Start XID_Continue* # read in NFKC # §3.5.1 string-literal := '"' string-char* '"' | '"""' triple-char* '"""' # triple-quoted (may span lines) | raw-prefix '"' raw-char* '"' # raw | raw-prefix '"""' raw-triple-char* '"""' # raw, triple-quoted raw-prefix := 'r' | 'R' raw-char := | '\' # both kept: the backslash escapes nothing raw-triple-char := | '\' triple-char := | # written as itself, not escaped | string-escape # the escapes of string-char string-char := | string-escape string-escape := '\\' # backslash | '\"' # double quote | '\' # single quote | '\n' # line feed (U+000A) | '\t' # horizontal tab (U+0009) | '\r' # carriage return (U+000D) | '\a' # bell (U+0007) | '\b' # backspace (U+0008) | '\f' # form feed (U+000C) | '\v' # vertical tab (U+000B) | '\x' hex hex # one codepoint <= U+00FF, two hex digits | '\u' hex{4} # one codepoint ≤ U+FFFF, UTF-8 encoded | '\U' hex{8} # one codepoint ≤ U+10FFFF, UTF-8 encoded | '\N{' name '}' # a codepoint by its Unicode name | '\' octal octal? octal? # one codepoint, up to three octal digits | '\' # nothing: the string goes on at the next line | '\' # both kept: the backslash escapes nothing octal := '0'..'7' hex := '0'..'9' | 'a'..'f' | 'A'..'F' name := ( 'A'..'Z' | 'a'..'z' | '0'..'9' | ' ' | '-' )+ # a Unicode character name, matched case-insensitively # §3.5.2 int-literal := dec-int | hex-int | oct-int | bin-int dec-int := dec-digit ( '_'? dec-digit )* hex-int := '0' ( 'x' | 'X' ) '_'? hex-digit ( '_'? hex-digit )* oct-int := '0' ( 'o' | 'O' ) '_'? oct-digit ( '_'? oct-digit )* bin-int := '0' ( 'b' | 'B' ) '_'? bin-digit ( '_'? bin-digit )* dec-digit := '0'..'9' oct-digit := '0'..'7' bin-digit := '0' | '1' # §3.5.3 bool-literal := 'True' | 'False' # §3.5.5 datetime-literal := 'datetime' '(' '"' rfc3339-utc '"' ')' rfc3339-utc := date 'T' time frac? 'Z' date := dec-digit dec-digit dec-digit dec-digit '-' dec-digit dec-digit '-' dec-digit dec-digit # YYYY-MM-DD time := dec-digit dec-digit ':' dec-digit dec-digit ':' dec-digit dec-digit # HH:MM:SS frac := '.' dec-digit{1,9} # 1..9 fractional-second digits # §3.5.6 duration-literal := 'duration' '(' '"' iso8601-duration '"' ')' iso8601-duration := '-'? 'P' days? ( 'T' hours? minutes? seconds? )? # >= one term days := dec-digit+ fraction? 'D' hours := dec-digit+ fraction? 'H' minutes := dec-digit+ fraction? 'M' seconds := dec-digit+ fraction? 'S' fraction := '.' dec-digit{1,9} # lowest-order term only # §3.5.7 real-literal := int-part '.' frac-part? exponent? # 3.5, 5. (trailing dot), 1.5e-3 | '.' frac-part exponent? # .5 (leading dot), .5e2 | int-part exponent # 1e5 (exponent, no point) int-part := dec-digit ('_'? dec-digit)* frac-part := dec-digit ('_'? dec-digit)* exponent := ('e' | 'E') ('+' | '-')? dec-digit ('_'? dec-digit)* # §3.5.8 blob-literal := hex-blob | b64-blob hex-blob := 'blob' '(' '"' ( hex-digit hex-digit )* '"' ')' b64-blob := 'blob_b64' '(' '"' base64-char* '"' ')' hex-digit := dec-digit | 'a'..'f' | 'A'..'F' base64-char := 'A'..'Z' | 'a'..'z' | dec-digit | '+' | '/' | '=' # §3.5.9 list-literal := '[' ( display-item ( ',' display-item )* ','? )? ']' display-item := expr | '*' bitor-expr # an element, or a spread (§8.15) # §3.5.10 fstring-literal := fstring-prefix '"' ( fstring-char | replacement-field )* '"' fstring-prefix := 'f' | 'F' | raw-prefix ( 'f' | 'F' ) | ( 'f' | 'F' ) raw-prefix # raw, §3.5.1 fstring-char := string-char (§3.5.1) # '{{' and '}}' denote literal '{' and '}' replacement-field := '{' expr '='? ( '!' ('r' | 's') )? ( ':' format-spec )? '}' format-spec := ( fill? align )? sign? 'z'? '#'? '0'? width? grouping? ( '.' precision )? format-type? # §8.13 fill := align := '<' | '>' | '^' | '=' sign := '+' | '-' | ' ' width := dec-digit+ grouping := ',' | '_' precision := dec-digit+ format-type := 's' | 'd' | 'x' | 'X' | 'o' | 'b' | 'c' | 'n' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | '%' # §3.5.11 tuple-literal := '(' ( display-item ( ',' display-item )* ','? )? ')' # a `*` item alone needs its comma (§8.15) # §3.5.12 dict-literal := '{' ( dict-item ( ',' dict-item )* ','? )? '}' dict-item := expr ':' expr | '**' bitor-expr # §3.5.13 set-literal := '{' display-item ( ',' display-item )* ','? '}' # §3.5.14 scientific-literal := 'scientific' '(' '"' scientific-numeral '"' ')' scientific-numeral := '-'? mantissa ( 'e' | 'E' ) '-'? dec-digit+ mantissa := dec-digit+ ( '.' dec-digit+ )? # a nonzero magnitude # §3.5.15 imag-literal := ( dec-int | real-literal ) ( 'j' | 'J' ) # §3.9.1 declaration := '#' decl-blank* 'dke:' decl-blank* decl-setting decl-blank+ decl-setting decl-blank* decl-setting := decl-key '=' decl-name ('.' decl-version)? decl-key := 'edition' | 'language' decl-name := ('a'..'z')+ decl-version := ('1'..'9') ('0'..'9')* decl-blank := ' ' | # §4.1 source := option-verb NEWLINE | module-body module-body := docstring? top-item+ docstring := STRING NEWLINE # documents what it opens; nothing runs it top-item := decl | class-decl | rule-decl # decl §4.2, class-decl §13.1, rule-decl §14 | constraint-decl # constraint-decl §14.10 | import-stmt | stmt # import-stmt §4.3, stmt §4.3 (incl. the four writes) — see Modules option-verb := '--' IDENT IDENT? # §4.2 decl := 'def' IDENT '(' params? ')' ( '->' return-type )? ':' NEWLINE INDENT ( docstring stmt* | stmt+ ) DEDENT return-type := type | 'value' | 'None' # `value`: a store fold's result, narrowed by the caller (§4.2); `None`: what no annotation says params := param-item ( ',' param-item )* # which lists are read: §4.2 param-item := param | var-param | '/' # what stands before it takes no keyword (§4.2) | '*' # what follows it takes no position (§4.2) param := IDENT ':' param-type ( '=' const-expr )? # a default value var-param := '*' IDENT ':' binding-type # variadic: surplus positionals → tuple const-expr := expr # a literal, or an operation over literals param-type := binding-type | module-name '.' IDENT # a class an imported module declares (§13.2) binding-type := type type := type-name | IDENT # a class name (§13.1), in EVERY type position (§5.5) | 'list' '<' type '>' | 'set' '<' type '>' | 'dict' '<' type ',' type '>' | 'tuple' '<' type ( ',' type )* '>' | 'iterator' '<' type '>' # §8.6 | 'dict_keys' '<' type '>' | 'dict_values' '<' type '>' # dict views (§5.1) | 'dict_items' '<' type ',' type '>' | collection-word '[' type ( ',' type )* ( ',' '...' )? ']' # any of them in CPython's brackets, the same type (§5.5) type-name := scalar-name | 'active_claim' | 'claim_list' | 'proof_tree' | 'diagnosis' # read-result types (§5.1) | 'subject_set' | 'string_list' | 'verify_result' | 'result_type_set' | 'slice' # a slice value (§5.3) scalar-name := 'string' | 'int' | 'bool' # primitives | 'datetime' | 'duration' | 'real' | 'blob' | 'scientific' # value-class scalars (§5.5) | 'complex' | 'str' | 'float' # CPython's names for `string` and `real` (§5.5) collection-word := 'list' | 'set' | 'dict' | 'tuple' | 'iterator' | 'dict_keys' | 'dict_values' | 'dict_items' # §4.3 stmt := simple-stmt ( ';' simple-stmt )* ';'? NEWLINE | block-stmt simple-stmt := assign-stmt | print-stmt | call-stmt | verb-call-stmt | commit-stmt | rollback-stmt | import-stmt | return-stmt # §9.2.8 | raise-stmt | assert-stmt | rebind-stmt # changes a collection, §8.14 | del-stmt # §8.14 | 'pass' # a no-op | '...' # a no-op, as `pass` is | expr-list # an expression statement: it runs, and nothing keeps its value | 'break' # loop control, §9.2.7 | 'continue' block-stmt := match-stmt | class-match # dispatch by class, §13.5 | for-stmt | if-stmt | try-stmt | branch-block | world-block # §9.2.9 assign-stmt := IDENT '=' expr-list | IDENT ':' binding-type # an annotation with no value; it binds nothing | IDENT ':' binding-type '=' expr-list # annotated assignment; the type must agree with the initializer, and only a name takes one (§6.4) | chain-target ( '=' chain-target )+ '=' expr-list # chained assignment; every target binds one value, left to right (§8.12) | IDENT aug-op expr-list # augmented assignment on a bare name; `x op= e` is `x op e`, changed in place on a list or set (a field names a source, so it uses `remember(...)`, not `op=`) | top-target ( ',' top-target )+ '=' expr-list # unpacking a tuple or list; nested targets + one optional `*rest` (§8.11) | target-group '=' expr-list # a parenthesised or bracketed target list (§8.11) expr-list := display-item ( ',' display-item )* ','? # a bare comma-list; holding a comma, it is a tuple (§3.5.11); a `*` item alone is none (§8.15) top-target := unpack-target | '*' unpack-target # a target, or THE single starred target (≤ one per list), which may be any target unpack-target := IDENT | element-target | target-group # a name, an element or a slice, or a nested target list, which may hold its own star target-group := '(' top-target ( ',' top-target )* ','? ')' # in parentheses; one target alone in them is that target | '[' top-target ( ',' top-target )* ','? ']' # in brackets; `[a]` is a list of one target (§8.11) | '(' ')' | '[' ']' # a list of no targets; the value must hold nothing (§8.11) chain-target := top-target ( ',' top-target )* ','? # a target or a target list, as an assignment takes one (§8.12) element-target := write-target subscript # an element or a slice, written as an element write writes it (§8.14) | postfix-expr subscript # an element of what an expression answers, which no name holds (§8.14) aug-op := '+=' | '-=' | '*=' | '/=' | '%=' | '//=' | '**=' | '|=' | '&=' | '^=' | '@=' rebind-stmt := write-target '.' rebind-method '(' arg-list? ')' # changes the collection the target reaches; a statement, never an expression (§8.14) | write-target '.' answer-method '(' arg-list? ')' # changes the collection and discards what the call answers; the call is an expression too (§8.14) | postfix-expr '.' ( rebind-method | answer-method ) '(' arg-list? ')' # the same, on what an expression answers, which no name holds (§8.14) | element-target '=' expr-list # an element or slice write; changes the collection the target reaches (§8.14) | element-target aug-op expr-list # `c[k] op= e` is `c[k] = c[k] op e`, the target and `k` evaluated once write-target := IDENT ( subscript | '.' IDENT '(' arg-list? ')' )* # a bound name, then any chain of subscripts and method calls; what it reaches is the collection changed (§8.14) subscript := '[' expr ( ',' expr )* ','? ']' # an index or a key; a comma makes it a tuple, `d[1, 2]` is `d[(1, 2)]` (§4.4) | '[' expr? ':' expr? ( ':' expr? )? ']' # a slice; only a list's is written (§8.14) rebind-method := 'append' | 'extend' | 'insert' | 'remove' | 'sort' | 'reverse' | 'clear' | 'add' | 'discard' | 'update' answer-method := 'pop' | 'setdefault' | 'popitem' # changes the collection and answers a value (§8.14) del-stmt := 'del' write-target subscript # removes one element, entry or slice from the collection the target reaches (§8.14) | 'del' postfix-expr subscript # an element of what an expression answers, which no name holds (§8.14) | 'del' '(' element-target ')' # in parentheses, that element (§8.14) print-stmt := 'print' '(' ( print-arg ( ',' print-arg )* )? ')' print-arg := expr # a value to render | '*' expr # each element of a collection, as an argument of its own (§8.15) | ( 'sep' | 'end' | 'flush' ) '=' expr # a keyword, read after the arguments | 'file' '=' 'None' # the default, which is the line itself | '**' dict-literal # its keys as keywords, each key a literal (§8.15) raise-stmt := 'raise' ( ( EXC-NAME ( '(' ( expr ( ',' expr )* ','? )? ')' )? | IDENT ) ( 'from' expr )? )? # a catchable refuse; any other value after `raise` is read as a raise and is a parse_error at the value (§4.3) assert-stmt := 'assert' expr ( ',' expr )? # Python assertion (the absence-assertion verb, §7.1, is separate) call-stmt := IDENT '(' arg-list? ')' verb-call-stmt := IDENT '(' arg-list? ')' # a built-in verb call (§7); the head IDENT is a verb head-word commit-stmt := 'commit' rollback-stmt := 'rollback' import-stmt := 'import' import-item ( ',' import-item )* # load each module, Python Reference §9.3; `math` and `cmath` name the built-in namespaces, §8.16, `vector` one more, §8.17, and `struct` one more, §8.18 | 'from' IDENT 'import' import-names # bind names a module declares | 'from' IDENT 'import' '(' import-names ','? ')' import-names := import-name ( ',' import-name )* import-name := IDENT ( 'as' IDENT )? # bind the name, or bind it as the name after `as` import-item := module-name ( 'as' IDENT )? # bind the module, or bind it as the name after `as` module-name := IDENT ( '.' IDENT )* return-stmt := 'return' expr-list? # early exit / value, §9.2.8 match-stmt := 'match' expr-list ':' NEWLINE INDENT case-clause+ DEDENT # two or more items, or one and a comma, are the tuple written without its parentheses (§3.5.11), as CPython reads a subject case-clause := 'case' class-label ( 'if' expr )? ':' arm-body # §6.2 | 'case' elem-pattern ( 'if' expr )? ':' arm-body # structural: an alternative is a `pattern`, §6.2 | 'case' value-pattern ( 'if' expr )? ':' arm-body # value patterns, §6.2 | 'case' 'default' ':' arm-body # never guarded, §6.2 class-label := IDENT ( '|' IDENT )* # alternatives, §6.2 | IDENT 'as' IDENT # `as` binds ONE class, never across `|` value-pattern := value-alt ( '|' value-alt )* ( 'as' IDENT )? value-alt := case-literal | '_' | IDENT | class-pattern # IDENT: a capture, a name that is no # class label, and alone; `_` last class-pattern := IDENT ( '.' IDENT )? '(' ( elem-pattern ( ',' elem-pattern )* ','? )? ')' # a class tested, §6.2 case-literal := '-'? ( INT | REAL ) | STRING+ | 'True' | 'False' | '-'? IMAG | '-'? ( INT | REAL ) ( '+' | '-' ) IMAG # a complex, §3.5.15 arm-body := simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT pattern := seq-pattern | map-pattern # a SHAPE label, §6.2 seq-pattern := '[' ( elem-pattern ( ',' elem-pattern )* ( ',' '*' IDENT )? | '*' IDENT )? ']' # a `*` MUST be last map-pattern := '{' ( case-literal ':' elem-pattern ( ',' case-literal ':' elem-pattern )* )? '}' # no key named twice, §6.2 elem-pattern := elem-alt ( '|' elem-alt )* ( 'as' IDENT )? # the same names bound by # each; only the last may match every # value; IDENT is not `_` elem-alt := IDENT | '_' | case-literal | pattern | class-pattern # one per §6.2 element row, plus # `'*' IDENT` above, whose position # is constrained, and the `|` and the # `as` above for-stmt := 'for' top-target ( ',' top-target )* ','? 'in' expr ':' NEWLINE INDENT stmt+ DEDENT # a target list unpacks each element — the SAME list an assignment takes (§8.11) # (an element target among them is written each iteration; `top-target` is defined with `assign-stmt` above) if-stmt := 'if' expr ':' NEWLINE INDENT stmt+ DEDENT ( 'elif' expr ':' NEWLINE INDENT stmt+ DEDENT )* ( 'else' ':' NEWLINE INDENT stmt+ DEDENT )? try-stmt := 'try' ':' NEWLINE INDENT stmt+ DEDENT handler* handler := 'except' 'refuse' ( 'as' IDENT )? ':' NEWLINE INDENT stmt+ DEDENT | 'except' 'engine_error' ( 'as' IDENT )? ':' NEWLINE INDENT stmt+ DEDENT branch-block := 'with' 'branch' '(' STRING ')' ':' NEWLINE INDENT stmt+ DEDENT world-block := 'with' 'world' '(' expr ')' ':' NEWLINE INDENT stmt+ DEDENT # `world` is a soft keyword here arg-list := arg ( ',' arg )* arg := expr # positional | '*' expr # a spread: each element a positional argument of its own (§8.15) | IDENT '=' expr # a keyword argument # §4.4 expr := cond-expr cond-expr := or-expr ( 'if' or-expr 'else' expr )? # ternary; right-assoc; lower than `or` or-expr := and-expr ( 'or' and-expr )* and-expr := cmp-expr ( 'and' cmp-expr )* cmp-expr := bitor-expr ( cmp-op bitor-expr )* cmp-op := '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in' | 'is' | 'is' 'not' bitor-expr := bitxor-expr ( '|' bitxor-expr )* # set union, §8.6 bitxor-expr := bitand-expr ( '^' bitand-expr )* # set symmetric difference bitand-expr := add-expr ( '&' add-expr )* # set intersection add-expr := mul-expr ( ( '+' | '-' ) mul-expr )* mul-expr := unary-expr ( ( '*' | '/' | '//' | '%' | '@' ) unary-expr )* unary-expr := ( 'not' | '-' | '+' ) unary-expr | pow-expr pow-expr := postfix-expr ( '**' unary-expr )? # right-assoc; tighter than unary `-` postfix-expr := primary-expr postfix* postfix := '.' IDENT '(' arg-list? ')' # method call (§13.4; string methods §5.3) | '.' IDENT # field access | '[' display-item ( ',' display-item )* ','? ']' # indexing (string / collection, §5.3); a comma or a `*` makes the index a tuple (§8.15) | '[' expr? ':' expr? ( ':' expr? )? ']' # slice (string / list / tuple, §5.3) primary-expr := STRING | INT | 'True' | 'False' | fstring-literal # formatted string literal (§3.5.10) | list-display # list literal / comprehension (§3.5.9, §8.9) | brace-display # dict/set literal or comprehension (§3.5.12, §3.5.13, §8.9) | tuple-display # tuple literal / grouping (§3.5.11) | builtin-call # zip / enumerate (§8.6), abs / round (§8.3) | namespace-member # math / cmath (§8.16), vector (§8.17), struct (§8.18) | IDENT '(' expr ')' # instance constructor (§13.2) | module-name '.' IDENT '(' expr ')' # module-qualified construction (Python Reference §9.6) | value-call # value-script invocation (§6.8) | hypothesis # suppose (§15) | absence-hypothesis # suppose_absent (§15.1) | IDENT | path-expr | IDENT '(' arg-list? ')' # a built-in read verb call, §7 value-call := IDENT '(' arg-list? ')' # a value script's call, in expression position list-display := '[' ( display-item ( ',' display-item )* ','? )? ']' # list literal (§3.5.9); a spread, §8.15 | '[' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ ']' # list comprehension (§8.9) brace-display := '{' ( dict-item ( ',' dict-item )* ','? )? '}' # dict literal (§3.5.12) | '{' display-item ( ',' display-item )* ','? '}' # set literal (§3.5.13) | '{' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ '}' # set comprehension (§8.9) | '{' expr ':' expr ( 'for' IDENT 'in' expr ( 'if' expr )* )+ '}' # dict comprehension (§8.9) tuple-display := '(' expr ')' # grouping (no comma) | '(' ( display-item ( ',' display-item )* ','? )? ')' # tuple literal (§3.5.11); a `*` item alone needs its comma builtin-call := ( 'zip' | 'enumerate' | 'reversed' | 'range' | 'abs' | 'round' | 'divmod' | 'str' | 'int' | 'float' | 'list' | 'set' | 'dict' | 'isinstance' | 'issubclass' ) '(' ( ns-arg ( ',' ns-arg )* )? ')' # zip/enumerate/reversed/range §8.6, abs/round/divmod §8.3, conversions §8.13, isinstance §13.9, issubclass §13.10; a spread §8.15 namespace-member := ( 'math' | 'cmath' | 'vector' | 'struct' ) '.' IDENT ( '(' ( ns-arg ( ',' ns-arg )* ( ',' IDENT '=' expr )* )? ')' )? # §8.16–§8.18; a constant takes no call, a function takes one ns-arg := expr | '*' expr # a value, or a spread of its elements (§8.15) path-expr := IDENT ( '.' IDENT )+ # K.s.a slot-grammar dotted path # §8.9 comp-clause := 'for' comp-target ( ',' comp-target )* ','? 'in' iter-expr ( 'if' cond-expr )* comp-target := name-target | '*' name-target # THE single starred target (<= one per list), a name or a nesting name-target := IDENT | '(' comp-target ( ',' comp-target )* ','? ')' # a name, or a nesting of names, which may hold its own star; an element target needs a `for` statement (§8.11) | '[' comp-target ( ',' comp-target )* ','? ']' # a nesting in brackets | '(' ')' | '[' ']' # a nesting of no names; each element must hold nothing iter-expr := or-expr # a conditional expression needs parentheses here list-comprehension := '[' expr comp-clause+ ']' set-comprehension := '{' expr comp-clause+ '}' dict-comprehension := '{' expr ':' expr comp-clause+ '}' generator-argument := expr comp-clause+ # bare: the only argument of a call that reads it whole | '(' expr comp-clause+ ')' # parenthesized: an argument of such a call # §8.19 named-expr := IDENT ':=' expr # §13.1 class-decl := 'class' IDENT ( '(' IDENT? ')' )? ':' NEWLINE INDENT ( docstring class-member* | class-member+ ) DEDENT class-member := field-decl | method-def | 'pass' NEWLINE # declares nothing field-decl := IDENT ':' scalar-name NEWLINE | computed-field # a field with an initializer (§13.7) # §13.2 instance-ctor := IDENT '(' expr ')' # the class name applied to a name expression # §13.4 method-def := void-method | value-method void-method := 'def' IDENT '(' 'self' ( ',' param-item )* ')' ':' NEWLINE INDENT ( docstring stmt* | stmt+ ) DEDENT value-method := 'def' IDENT '(' 'self' ( ',' param-item )* ')' '->' return-type ':' NEWLINE INDENT docstring? stmt* 'return' expr NEWLINE DEDENT # §13.5 class-match := 'match' IDENT ':' NEWLINE INDENT class-case+ default-case? DEDENT class-case := 'case' IDENT ':' ( simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT ) default-case := 'case' 'default' ':' ( simple-stmt NEWLINE | NEWLINE INDENT stmt+ DEDENT ) # §13.7 computed-field := IDENT ':' computed-type '=' expr NEWLINE computed-type := 'int' | 'real' | 'float' | 'bool' | 'complex' # §14.1 rule-decl := '@rule' ( '(' 'kind' '=' rule-kind ')' )? NEWLINE 'def' IDENT '(' ')' ':' NEWLINE INDENT 'for' subject-binding ( ',' subject-binding )* ':' NEWLINE INDENT 'if' premise ( 'and' premise )* ':' NEWLINE INDENT conclusion NEWLINE DEDENT DEDENT DEDENT subject-binding := IDENT 'in' kind-name kind-name := IDENT # the kind whose fields the name reads rule-kind := '"definition"' | '"causal"' | '"empirical"' | '"policy"' | '"heuristic"' # §14.2 premise := comparison | equality | join | field-cmp | relation | absence | not-asserted | known-absent | nonexistence | source-test | trust-test | text-test comparison := IDENT '.' IDENT ord-op ( '-'? ( INT | REAL ) | SCIENTIFIC | STRING ) # o.total > 1000, o.price > 19.99, # o.mass > scientific("1.0e4"), o.name < "m" equality := IDENT '.' IDENT eq-op literal # o.lic == "MIT", o.total != 5 join := IDENT '.' IDENT eq-op IDENT # c.caller == f field-cmp := IDENT '.' IDENT ( eq-op | ord-op ) IDENT '.' IDENT # o.delivered > o.deadline relation := IDENT '.' IDENT '.' IDENT eq-op literal # Reach.x.y == True absence := 'absent' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # absent(o.shipment), absent(Gap.x.y) not-asserted := 'not_asserted' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # not_asserted(b.flies) known-absent := 'known_absent' '(' IDENT '.' IDENT ( '.' IDENT )? ')' # known_absent(p.chip) nonexistence := 'none' '(' IDENT 'in' IDENT ':' premise ( 'and' premise )* ')' # none(c in Call: c.caller == f) source-test := 'source' '(' IDENT '.' IDENT ')' ( eq-op STRING | '==' IDENT ) # source(r.temp) == "lab", # source(r.temp) == s trust-test := 'trust' '(' IDENT '.' IDENT ')' eq-op ( '"asserted"' | '"derived"' ) # trust(s.label) == "derived" text-test := text-cell '.' ( 'startswith' | 'endswith' ) '(' text-part ')' | text-part 'in' text-cell # r.path.startswith("/srv/"), # t.name.endswith(".test"), "admin" in a.role text-cell := IDENT '.' IDENT ( '.' IDENT )? text-part := STRING | IDENT '.' IDENT eq-op := '==' | '!=' ord-op := '<' | '<=' | '>' | '>=' literal := STRING | '-'? ( INT | REAL ) | SCIENTIFIC | complex | BLOB | 'True' | 'False' # §3.5; the same set `suppose` takes (§15) complex := '-'? IMAG | '-'? ( INT | REAL ) ( '+' | '-' ) IMAG # 2j, 1+2j, -0.5-1.5j (§3.5.15) # §14.3 conclusion := cell '=' ( literal | '-' ( INT | REAL ) | fold | copy | text ) # o.review = True, r.adjustment = -5, # r.rate = 0.75, c.order_count = count(o), # d.owner_name = p.name, # d.line = f"{d.title} cites {d.cites}" | 'assert' '(' cell ')' # assert(p.chip) — known to be absent cell := IDENT '.' IDENT # a subject and one of its fields | IDENT '.' IDENT '.' IDENT # a kind, a subject, a field copy := IDENT '.' IDENT # p.name — a field of a bound subject | IDENT '.' IDENT '.' IDENT # Dist.x.y — a relation's value for a pair text := # f"{p.name} ({d.room})" fold := fold-name '(' IDENT ')' # count(o) — fold the subjects | fold-name '(' IDENT '.' IDENT ')' # sum(o.amount) — fold one field fold-name := # count, sum, avg, argmax, … # §14.10 constraint-decl := '@constraint' NEWLINE 'def' IDENT '(' ')' ':' NEWLINE INDENT 'for' subject-binding ':' NEWLINE INDENT 'if' constraint-premise ( 'and' constraint-premise )* ':' NEWLINE INDENT 'violated' NEWLINE DEDENT DEDENT DEDENT constraint-premise := IDENT '.' IDENT ( ord-op | eq-op ) literal # a comparison against a literal | IDENT '.' IDENT '.' IDENT eq-op literal # the same, over a relation (§14.2) | 'absent' '(' IDENT '.' IDENT ')' # an absence # §15 hypothesis := 'suppose' '(' ( path-expr ',' literal ',' )+ path-expr ')' # §15.1 absence-hypothesis := 'suppose_absent' '(' ( path-expr ',' )+ path-expr ')' ``` --- ## Appendix B — Reserved words (complete list) ``` and as assert async await break class continue def del elif else except False finally for from global if import in is lambda None nonlocal not null or pass raise return True try while with yield ``` The type names of §5.5 (`string`, `int`, `claim_list`, …) are contextual, not reserved; programs SHOULD nonetheless avoid them as user identifiers. `import` ([Python Reference §9.3](/ref/dke/python/#import)) is a reserved word (§3.7), as it is in Python, and begins the import statement at statement head, immediately before a module name. --- ## Appendix C — Sample programs (informative) These sample programs are **self-contained**: each takes no arguments and names its own subjects and values inline, so it runs exactly as shown — paste it, run it, read the result. Parameterised scripts (a signature with typed parameters, run with positional arguments over the wire) are specified normatively in [MCP Reference §4.2](/ref/dke/mcp/#run-execute) and the grammar (§4); the samples here stay self-contained so they need no external invocation input. ### C.1 Trivial smoke ``` # smoke-trivial.dpy — a write, an exhaustive match, and a length read. def smoke_trivial(): remember(Cat.felix.color, "orange", "op1") cur = current(Cat.felix.color) match cur: case active_claim: print("color = " + str(cur.value) + " (per " + str(cur.source) + ")") case empty: print("no color claim") hist = get(Cat.felix.color) print("history has " + str(hist.length) + " claim(s)") ``` Run it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). ### C.2 Audit walk ``` # audit-subject.dpy — read cluster + length composition. def audit_subject(): remember(Cat.felix.color, "orange", "audit") print("subject Cat.felix") attrs = list_attributes(Cat) print("attribute count = " + str(attrs.length)) cur = current(Cat.felix.color) print("current color inspected") hist = get(Cat.felix.color) print("history depth = " + str(hist.length)) ``` ### C.3 Try with refuse handling under a branch ``` # experimental write under a branch with explicit rollback. def try_paint(): with branch("experiment"): try: remember(Cat.felix.color, "tigret", "audit") after = current(Cat.felix.color) if after.value == "tigret": commit else: rollback except refuse as r: print("paint refused: " + str(r.reason)) rollback ``` ### C.4 Classes and methods (§13) ``` # classes.dpy — a class with a typed field, a void method, and a value # method; a def binds an instance and calls both. class Sensor: temp: string def record(self, v: string, src: string): remember(self.temp, v, src) # field write def label(self) -> string: return "sensor" # value method def demo(): s = Sensor("room-a") # instance binding s.record("21", "op1") # void method call name = s.label() # value method call reading = s.temp # field read match reading: case active_claim: print(name + " = " + str(reading.value) + " per " + str(reading.source)) case empty: print(name + ": no reading") ``` ### C.5 Datetime round-trip (§3.5.5, §5.6) ``` # datetime-round-trip.dpy — a datetime value keeps its type across write/read. def datetime_round_trip(): remember(Event.launch.at, datetime("2026-01-02T03:04:05Z"), "ops") cur = current(Event.launch.at) match cur.value: case datetime as d: print("launch at " + str(d)) case default: print("not a datetime") ``` ### C.6 Duration round-trip (§3.5.6, §5.6) ``` # duration-round-trip.dpy — a duration keeps its type and reads back canonical. def duration_round_trip(): remember(Task.build.budget, duration("PT90M"), "ops") cur = current(Task.build.budget) match cur.value: case duration as u: print("budget = " + str(u)) case default: print("not a duration") ``` ### C.7 Real round-trip (§3.5.7, §5.6) ``` # real-round-trip.dpy — a real keeps its type and reads back in canonical form. def real_round_trip(): remember(Sensor.a.reading, 3.5, "ops") cur = current(Sensor.a.reading) match cur.value: case real as r: print("reading = " + str(r)) case default: print("not a real") ``` ### C.8 Blob round-trip (§3.5.8, §5.6) ``` # blob-round-trip.dpy — a blob keeps its type and reads back as canonical hex. def blob_round_trip(): remember(Doc.d1.payload, blob("48656C6C6F"), "ops") cur = current(Doc.d1.payload) match cur.value: case blob as b: print("payload = " + str(b)) case default: print("not a blob") ``` ### C.9 Real arithmetic (§3.5.7, §8.3, §13.3) ``` # real-arithmetic.dpy — reals compute; arithmetic promotes an int to a real, # while a declared return and a selection hand back the int they were given, so # one real field can hold 1 and 1.0; a computed real writes through and reads # back as a real. class Meter: reading: real def one() -> real: return 1 def real_arithmetic(): sum = 2.5 + 1.25 print("sum = " + str(sum)) # 3.75 print("half = " + str((7.0 / 2))) # 3.5 — int promotes, true division print("scaled = " + str((sum * 2))) # 7.5 — real * int promotes remember(Sensor.a.total, sum, "ops") cur = current(Sensor.a.total) match cur.value: case real as r: print("stored = " + str(r)) # 3.75 — still a real case default: print("not a real") if sum > 3.0: print("above threshold") v = one() w = 1 + 0.0 remember(Meter.k.reading, v, "ops") remember(Meter.j.reading, w, "ops") t = 1 if True else 2.5 print(v, w, t, v == w) # 1 1.0 1 True ``` Invoked, it writes the two numbers into the one `real` field as they were produced: ``` OK script real_arithmetic(): 3 claims written, 6 prints print sum = 3.75 print half = 3.5 print scaled = 7.5 remember Sensor.a.total = 3.75 print stored = 3.75 print above threshold remember Meter.k.reading = 1 remember Meter.j.reading = 1.0 print 1 1.0 1 True ``` ### C.10 Base64 blob input (§3.5.8) ``` # base64-blob.dpy — a blob written with the base64 spelling reads back as the # same canonical hex as the equivalent blob("…") hex form. def base64_blob(): remember(Doc.d1.payload, blob_b64("SGVsbG8="), "ops") cur = current(Doc.d1.payload) match cur.value: case blob as b: print("payload = " + str(b)) # 48656C6C6F — canonical hex case default: print("not a blob") ``` ### C.11 As-of read (§7.1) ``` # as-of-read.dpy — current/get take an optional datetime to read a value as of # another time. For an always-valid fact that is the present value; with a # validity window an interior time selects the value valid then. def as_of_read(): remember(Rate.usd.pct, "3.0", "src") past = current(Rate.usd.pct, datetime("2020-01-01T00:00:00Z")) print("rate as of 2020: " + str(past.value)) ``` ### C.12 Validity windows (§7.1) ``` # validity-window.dpy — two writes at one cell with non-overlapping validity # windows (distinct sources) model a value that changed over time; two as-of # reads at interior times select the two values. def validity_window(): remember(Sensor.room1.status, "cold", "reading-2019", datetime("2019-01-01T00:00:00Z"), datetime("2021-01-01T00:00:00Z")) remember(Sensor.room1.status, "warm", "reading-2021", datetime("2021-01-01T00:00:00Z"), datetime("2023-01-01T00:00:00Z")) past = current(Sensor.room1.status, datetime("2020-06-01T00:00:00Z")) print("status as of 2020: " + str(past.value)) recent = current(Sensor.room1.status, datetime("2022-06-01T00:00:00Z")) print("status as of 2022: " + str(recent.value)) ``` ### C.12.1 Reading back a scheduled commitment (§7.1) ``` # scheduled-commitment.dpy — a fact whose validity window has not yet opened is # not part of the present, so the traversal verbs report nothing about it until # they are given the time to answer as of. The same anchor that reads a cell # reads the enumerations leading to it, so walking the enumerations reaches the # fact. def scheduled_commitment(): 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") subs = list_subjects(Release, when) print("subjects: " + str(subs)) attrs = list_attributes(Release.v2, when) print("attributes: " + str(attrs)) vals = list_values(Release.ships, when) print("values: " + str(vals)) ``` ### C.13 Aggregate query (§7.1.1) ``` # aggregate-demo.dpy — read-only summary statistics over a stored attribute. def aggregate_demo(): remember(Reading.r1.value, 10, "sensor") remember(Reading.r2.value, 20, "sensor") remember(Reading.r3.value, 30, "sensor") mean = avg(Reading.value) print("mean = " + str(mean)) n = count(Reading.value) print("readings = " + str(n)) # identity / positional folds (§7.1.1): distinct values, which subject holds # the max, and the earliest-recorded reading. kinds = distinct(Reading.value) print("distinct = " + str(kinds)) hottest = argmax(Reading.value) print("hottest subject = " + str(hottest)) oldest = first(Reading.value) print("first recorded = " + str(oldest)) ``` ### C.14 Computed field (§13.7) ``` # computed-field-demo.dpy — a field the engine derives and maintains. # Declaring `total` with an initializer installs a derivation: whenever an # Order's price and qty are known, total is price × qty. class Order: price: int qty: int total: int = price * qty def computed_demo(): remember(Order.o1.price, 10, "sales") remember(Order.o1.qty, 5, "sales") t = current(Order.o1.total) print("total = " + str(t.value)) ``` ### C.15 Standing rule (§14) ``` # standing-rule-demo.dpy — a named rule the engine applies automatically. # `needs_review` flags any order over 1000 with no shipment recorded; the # engine sets `review` on its own (and would withdraw it if a shipment were # later recorded). The demo writes one qualifying order and reads the flag. @rule def needs_review(): for o in Order: if o.total > 1000 and absent(o.shipment): o.review = True def rule_demo(): remember(Order.o1.total, 1500, "sales") r = current(Order.o1.review) print("review = " + str(r.value)) ``` ### C.16 Hypothesis (§15) ``` # hypothesis-demo.dpy — read a value under supposed facts, without committing. # The rule flags an order for review when it is over 1000 AND has shipped. One # supposition cannot satisfy a rule that needs two facts, so the second call # supposes both — recording nothing either way. @rule def needs_review(): for o in Order: if o.total > 1000 and o.shipment == "sent": o.review = True def hypothesis_demo(): remember(Order.o1.shipment, "sent", "ops") would = suppose(Order.o1.total, 2000, Order.o1.review) print("would review = " + str(would)) both = suppose(Order.o1.total, 2000, Order.o1.shipment, "sent", Order.o1.review) print("under both = " + str(both)) ``` ### C.17 A data module (Python Reference §9.1, §9.2) ``` # catalog.dpy — a module: a class schema and a data declaration. The body runs # on import (Python Reference §9.1); it declares no callable script (Python Reference §9.7). class Product: price: int remember(Product.widget.price, 500, "price-list") ``` Load it by name (service surface, [Python Reference §9.3](/ref/dke/python/#import)): ``` import catalog ``` ### C.18 Importing a module and a module-qualified read (Python Reference §9.3, §9.6) ``` # reporter.dpy — imports the catalog module (Python Reference §9.3) and reads one of its cells # with the module-qualified construction (Python Reference §9.6). import catalog def price_report(): p = catalog.Product("widget").price print("widget price = " + str(p.value)) ``` ``` import reporter ``` Invoking `reporter`'s script by its module-qualified address `reporter.price_report` ([Python Reference §9.6](/ref/dke/python/#namespacing)) against a store that has imported `catalog` (§C.17). The response header names the script's own call frame (`price_report()`); the module qualifier is the *address*, not part of the frame: ``` OK script price_report(): 1 print print widget price = 500 ``` ### C.19 String slicing, search, and replace (§5.2, §5.3, §8.6) ``` # string-ops.dpy — char access, slicing, search, tests, case, and trimming. def string_ops(): email = " Merlin@Langsyn.org " trimmed = email.strip() at = trimmed.find("@") print("local = " + str(trimmed[:at]).lower()) print("domain = " + str(trimmed[at + 1:])) print("first char = " + str(trimmed[0])) print("length = " + str(trimmed.length)) print("upper = " + str(trimmed.upper())) print("masked = " + str(trimmed.replace("@", " at "))) print("is org = " + str(trimmed.endswith(".org"))) print("starts M = " + str(trimmed.startswith("Merlin"))) print("dots = " + str(trimmed.count("."))) print("has at = " + str(("@" in trimmed))) print("no spam = " + str(("spam" not in trimmed))) print("tidy = [" + str("**Merlin**".strip("*")) + "]") print("reversed = " + str(trimmed[::-1])) print("every 2nd = " + str(trimmed[::2])) print("last = " + str(trimmed[-1])) print("tld = " + str(trimmed[-3:])) print("digits only = " + str("12345".isdigit())) print("all caps = " + str(trimmed.isupper())) print("at index = " + str(trimmed.index("@"))) ``` Run it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)). --- ### C.20 Constructed lists, comprehensions, and split/join (§3.5.9, §5.1, §5.3, §8.6, §8.9) ``` # collections.dpy — list literals, indexing/slicing, membership, # comprehensions, and the split/join string bridge. def collections(): nums = [3, 1, 4, 1, 5] print("nums = " + str(nums)) print("count = " + str(nums.length)) print("last = " + str(nums[-1])) head = nums[:2] print("head = " + str(head)) print("has 4 = " + str((4 in nums))) evens = [n for n in nums if n % 2 == 0] print("evens = " + str(evens)) words = "merlin,are,langsyn".split(",") print("words = " + str(words)) caps = [w.upper() for w in words] print("joined = " + str(" ".join(caps))) print("same = " + str(([1, 2] == [1, 2]))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script collections(): 9 prints print nums = [3, 1, 4, 1, 5] print count = 5 print last = 5 print head = [3, 1] print has 4 = True print evens = [4] print words = ['merlin', 'are', 'langsyn'] print joined = MERLIN ARE LANGSYN print same = True ``` ### C.21 Tuples, dicts, and sets (§3.5.11, §3.5.12, §3.5.13, §5.1, §5.3, §8.1, §8.6) ``` # collections2.dpy — tuple, dict, and set literals with their reads. def collections2(): point = (3, 4) print("point = " + str(point)) print("x = " + str(point[0])) ages = {"ana": 30, "bo": 25} print("ages = " + str(ages)) print("ana = " + str(ages["ana"])) print("has bo = " + str(("bo" in ages))) print("same = " + str((ages == {"bo": 25, "ana": 30}))) print("names = " + str(ages.keys())) tags = {"red", "green", "red", "blue"} print("tags = " + str(tags)) print("size = " + str(tags.length)) warm = {"red", "orange"} print("shared = " + str(tags.intersection(warm))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script collections2(): 10 prints print point = (3, 4) print x = 3 print ages = {'ana': 30, 'bo': 25} print ana = 30 print has bo = True print same = True print names = dict_keys(['ana', 'bo']) print tags = {'blue', 'green', 'red'} print size = 3 print shared = {'red'} ``` ### C.22 Dict and set comprehensions (§8.9) ``` # comprehensions2.dpy — build a dict and a set by bounded iteration. def comprehensions2(): nums = [1, 2, 3, 4] squares = {n: n * n for n in nums} print("squares = " + str(squares)) parity = {n % 2 for n in nums} print("parity = " + str(parity)) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script comprehensions2(): 2 prints print squares = {1: 1, 2: 4, 3: 9, 4: 16} print parity = {0, 1} ``` ### C.23 zip, enumerate, and partition (§8.6) ``` # zipping.dpy — pair collections into tuples, read the pairs once, and split a # string once. def zipping(): names = ["ana", "bo"] scores = [90, 85] paired = zip(names, scores) print("paired = " + str(paired)) print("read = " + str(list(paired))) print("again = " + str(list(paired))) ranked = list(enumerate(names)) print("ranked = " + str(ranked)) parts = "user@example.com".partition("@") print("parts = " + str(parts)) print("domain = " + str(parts[2])) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script zipping(): 6 prints print paired = print read = [('ana', 90), ('bo', 85)] print again = [] print ranked = [(0, 'ana'), (1, 'bo')] print parts = ('user', '@', 'example.com') print domain = example.com ``` ### C.24 Value scripts and value-calls (§4.2, §4.4, §9.2.8) ``` # value-scripts.dpy — value-returning scripts compose by expression: one is # nested inside another's arguments, and a branch-covered value script returns # a string on every path. def doubled(n: int) -> int: return n * 2 def summed(a: int, b: int) -> int: return a + b def classify(n: int) -> string: if n > 100: return "big" else: return "small" def value_scripts(): total = summed(doubled(10), doubled(11)) print("total = " + str(total)) print("nested = " + str(doubled(doubled(5)))) print("size = " + str(classify(total))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script value_scripts(): 3 prints print total = 42 print nested = 20 print size = small ``` ### C.25 Inheritance, override, super, dispatch, and isinstance (§13.8, §13.9) ``` # inheritance.dpy — a subclass extends a base, overrides a method and calls # super, dispatches polymorphically through a base-typed parameter, and tests an # instance's class with isinstance and a class match. class Animal: sound: string def speak(self): remember(self.sound, "generic", "op1") def describe(self) -> string: return "an-animal" class Dog(Animal): breed: string def speak(self): super().speak() remember(self.breed, "collie", "op1") def describe(self) -> string: return "a-dog" class Cat(Animal): def describe(self) -> string: return "a-cat" def label(a: Animal): print("desc=" + str(a.describe())) def inheritance(): animals = [Dog("d1"), Cat("c1")] for a in animals: label(a) d = Dog("d2") d.speak() br = d.breed match br: case active_claim: print("breed=" + str(br.value)) case empty: print("breed=?") print("is-animal=" + str(isinstance(d, Animal))) c = Cat("c2") match c: case Dog: print("class=dog") case Cat: print("class=cat") case default: print("class=other") ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script inheritance(): 2 claims written, 5 prints print desc=a-dog print desc=a-cat remember Dog.d2.sound = "generic" remember Dog.d2.breed = "collie" print breed=collie print is-animal=True print class=cat ``` --- ### C.26 Temporal canonical form, equality, and ordering (§3.5.5, §3.5.6, §8.1) ``` # temporal.dpy — datetimes and durations read back canonical, compare by value # rather than by spelling, and order (chronologically for instants, by length for # spans). `PT90M` and `PT1H30M` denote one span, so neither is shorter. def temporal(): start = datetime("2026-01-02T03:04:05Z") end = datetime("2026-01-02T03:04:05.500Z") short = duration("PT90M") same = duration("PT1H30M") longer = duration("PT2H") print("end-canonical=" + str(end)) print("span-canonical=" + str(short)) print("earlier=" + str(start < end)) print("same-span=" + str(short == same)) print("neither-shorter=" + str(short < same)) print("shorter-than=" + str(short < longer)) print("ordered=" + str(sorted([longer, short]))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script temporal(): 7 prints print end-canonical=2026-01-02T03:04:05.5Z print span-canonical=PT1H30M print earlier=True print same-span=True print neither-shorter=False print shorter-than=True print ordered=[PT1H30M, PT2H] ``` --- ### C.27 Temporal arithmetic (§3.5.5, §3.5.6, §8.3) ``` # temporal-math.dpy — shift an instant by a length, measure the length between # two instants, and scale a length. Subtracting forwards from backwards gives a # negative span, and a shift respects the calendar. def temporal_math(): start = datetime("2026-01-02T09:00:00Z") shift = duration("PT7H30M") finish = start + shift print("finish=" + str(finish)) print("elapsed=" + str(finish - start)) print("double=" + str(shift * 2)) print("back=" + str(start - finish)) print("leap=" + str(datetime("2024-02-28T12:00:00Z") + duration("P1D"))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script temporal_math(): 5 prints print finish=2026-01-02T16:30:00Z print elapsed=PT7H30M print double=PT15H print back=-PT7H30M print leap=2024-02-29T12:00:00Z ``` ### C.28 Quotient and remainder, and reading backwards (§8.3, §8.6) ``` # quotient.dpy — split a division into its quotient and remainder in one call, # and walk a collection backwards. Both halves of divmod floor together, so a # negative dividend keeps the remainder positive. `reversed` returns an iterator, # which `list` and `join` each read to the end. def quotient(): print("even = " + str(divmod(9, 3))) print("odd = " + str(divmod(7, 2))) print("negative = " + str(divmod(-7, 2))) minutes = 135 hm = divmod(minutes, 60) print("duration = " + str(hm[0]) + "h" + str(hm[1]) + "m") print("countdown = " + str(list(reversed([1, 2, 3])))) print("backwards = " + "".join(reversed("stressed"))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script quotient(): 6 prints print even = (3, 0) print odd = (3, 1) print negative = (-4, 1) print duration = 2h15m print countdown = [3, 2, 1] print backwards = desserts ``` ### C.29 Type conversions and the spelling of a rendered bool (§8.13) ``` # conversions.dpy — every conversion §8.13 defines, in one script. A bool # renders `True`/`False`, the Python spelling, whether it was written as a # literal or read back from the store: `stored` renders a bool read from the # store, and `verified` renders a whole result type plus one of its declared # `string` fields. def conversions(): print("bool = " + str(True) + " " + str(False)) print("int = " + str(int(3.9)) + " " + str(int(-3.9)) + " " + str(int(True)) + " " + str(int(" -7 "))) print("float = " + str(float(1)) + " " + str(float("2.5")) + " " + str(float(True))) print("str = " + str(42) + " " + str(2.5)) print("roundtrip = " + str(bool(str(True) == "True"))) remember(Flag.f1.on, True, "ops") stored = get(Flag.f1.on) print("stored = " + str(stored)) checked = verify(Flag.f1.on, True) print("verified = " + str(checked) + " " + checked.actual) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script conversions(): 1 claim written, 7 prints print bool = True False print int = 3 -3 1 -7 print float = 1.0 2.5 1.0 print str = 42 2.5 print roundtrip = True remember Flag.f1.on = True print stored = 1 entry [True] print verified = verify_result match=True True ``` ### C.30 A class whose reasoning is counted two ways (§13.7, §14.7, §14.9) ``` # mixed-reasoning.dpy — one class carrying BOTH kinds of standing rule: a # computed field the engine derives, and an @rule you named. Compiled plainly # rather than as a module, so the names below are bare: `list(target = rules)` (§14.7) # names only `bigbox`, while `checkup` (§14.9) reports `rules=2`. Both are # right — a computed field is a standing rule with no name you gave it. class Box: w: int h: int area: int = w * h big: bool @rule def bigbox(): for b in Box: if b.area > 100: b.big = True def mixed_demo(): remember(Box.b1.w, 20, "yard") remember(Box.b1.h, 30, "yard") a = current(Box.b1.area) print("area = " + str(a.value)) f = current(Box.b1.big) print("big = " + str(f.value)) ``` Running `mixed_demo` as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script mixed_demo(): 2 claims written, 2 prints remember Box.b1.w = 20 remember Box.b1.h = 30 print area = 600 print big = True ``` Both values read `derived`: `area` from the computed field, `big` from the `@rule`. Against that store `list(target = rules)` answers `bigbox` and `checkup` answers `counts rules=2 unfirable=0 unreadable=0 declined=0 kinds=1 unread=0 constraints=0 unevaluable=0 violated=0`. ### C.31 Turning a point with `math` and `cmath` (§8.16) ``` # rotation.dpy — turn the point 3 + 4j a quarter turn, then move between its # two forms. Multiplying by cmath.exp(1j * theta) turns a complex number by the # angle theta; polar gives a modulus and an angle, and rect gives them back. # Each step is rounded, so the quarter turn is not exact in its last digit. import math import cmath def rotation(): p = 3 + 4j q = p * cmath.exp(1j * math.pi / 2) print("rotated = " + str(q)) r, phi = cmath.polar(p) print("modulus = " + str(r)) print("angle = " + str(phi) + " = " + str(math.atan2(4, 3))) print("back = " + str(cmath.rect(r, phi))) print("close = " + str(math.isclose(q.real, -4.0))) print("root = " + str(cmath.sqrt(-4))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script rotation(): 6 prints print rotated = (-4+3.0000000000000004j) print modulus = 5.0 print angle = 0.9272952180016122 = 0.9272952180016122 print back = (3+4j) print close = True print root = 2j ``` ### C.32 Transforming a list with `vector` (§8.17) ``` # transform.dpy — four readings, where bits 0 and 1 of an element's index are # its two axes. [[0.75, 0.25], [0.25, 0.75]] blends each pair, a quarter of # each into the other, applied along axis 0 and then axis 1. The 4 × 4 matrix # keeps a running total: along [1, 0] it runs in the list's own order, and # along [0, 1] it takes the middle two the other way round, because the first # axis named is the high bit of a row. The last line adds the first half of a # list of ints into the second, and ints stay ints. import vector def transform(): blend = [[0.75, 0.25], [0.25, 0.75]] xs = vector.apply([4.0, 8.0, 0.0, 4.0], blend, [0]) print("blended = " + str(vector.apply(xs, blend, [1]))) upto = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] print("totals = " + str(vector.apply([10, 20, 30, 40], upto, [1, 0]))) print("other order = " + str(vector.apply([10, 20, 30, 40], upto, [0, 1]))) print("added = " + str(vector.apply([1, 2, 3, 4], [[1, 0], [1, 1]], [1]))) ``` Running it as a one-shot with `run` ([MCP Reference §4.2](/ref/dke/mcp/#run-execute)) produces: ``` OK script transform(): 4 prints print blended = [4.0, 6.0, 2.0, 4.0] print totals = [10, 30, 60, 100] print other order = [10, 60, 40, 100] print added = [1, 2, 4, 6] ``` ### C.33 Numbers held as one blob with `struct` (§8.18) ``` # packing.dpy — numbers packed into one blob by a format, and read back. # `<` lays the values out little-endian with no padding; `@` aligns each to its # size, so the same three codes take more bytes. A list of complexes packs to # sixteen bytes each, and unpacks to the same numbers, signed zeros included. import struct def packing(): b = struct.pack("`, `>=`) is now judged, alone or with `absent()`, and is reported violated exactly when a `@rule` with the same premise would conclude; before, such a constraint held silently. §5.1: 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`. §10.4: `except engine_error` 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. §8.13: 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. §3.4: a name does not contain U+00B7, U+0387, U+013F or U+0140; each is refused wherever a name would hold it, as an unexpected character that the message names by its code point. No module of the DKE Library changes its stored size. - **rev 128** (2026-11-09) — §8.16: `cmath.exp`, `cmath.rect` and `cmath.sqrt` give each part of the result, real and imaginary, as its exact value rounded once, where CPython 3.11 rounds the parts it composes and again, so `cmath.exp(complex(1.3, 2.7)).imag` is `1.5681835703495033` where CPython answers `1.5681835703495035`; a part CPython gives its own way stays CPython's. Seven functions now depart from CPython's algorithm, not four. Example C.31's `cmath.rect` of `cmath.polar(3 + 4j)` gives back `(3+4j)`, where CPython answers `(3.0000000000000004+3.9999999999999996j)`. - **rev 127** (2026-11-09) — §8.3: `sum` over numbers holding a `real`, and no `complex`, is the exact sum of the start and every element rounded once, so `sum([0.1] * 10)` is `1.0` and `sum([1e16, 1.0, -1e16])` is `1.0` where CPython 3.11's `sum` answers `0.9999999999999999` and `0.0`; an `int` enters as the integer it is; a sum is refused as `arithmetic_overflow` only when its rounded value, or an all-`int` total, is out of range, so a running total that leaves the range on the way no longer refuses it; a zero sum is `-0.0` only when the start and every element are. §7.1.1: the store fold's `sum` and the list's agree. - **rev 122** (2026-11-08) — §8.16: `math` has `log10` and `log2`, `erf` and `erfc`, and `gamma` and `lgamma`, each the exact value rounded once, refusing a zero or negative argument to a logarithm and a pole of the gamma function with `math domain error` and a result past the finite range with `math range error`. `math.log(x, base)` is the logarithm of `x` to `base` rounded once, where Python's divides two rounded logarithms and rounds again, so `math.log(1000, 10)` is `3.0` where Python answers `2.9999999999999996`; the section says where a last digit may differ from Python's. - **rev 120** (2026-11-08) — §8.16: `math` has `asin`, `acos` and `atan`, each correctly rounded, and refusing outside its domain with `math domain error`; `hypot`, the distance from the origin over any number of coordinates, correctly rounded for two and Python's own algorithm for any other number; and `degrees` and `radians`, each the exact product rounded once. `degrees`, `radians` and `hypot` of two coordinates depart from Python's algorithm, which rounds twice, so a last digit may differ from Python's, and the section says where. A result past the largest `real`, where Python answers an infinity, refuses with `arithmetic_overflow`. - **rev 113** (2026-11-06) — §3.5.8: a write's transcript line gives a blob's hex whole up to 65,536 characters, and past that its length and first 8 bytes, marked as shortened; `print` and `str(…)` are never shortened. The forms a transcript gives every value past that length are stated in the MCP Reference, §4.2. - **rev 112** (2026-11-06) — §3.5.8: a blob is cut, joined and read a byte at a time as Python's `bytes` are: `b[i:j]` with a whole slice (negative bounds, a step) answers a blob, `b1 + b2` a blob, and `b[i]` an `int`, defined by three Python functions stated in full. Each is one operation and builds no cell; a position past either end is refused as `index_out_of_bounds` and a step of zero as `slice_step_zero`; "It is not otherwise operated on" now follows the three. - **rev 111** (2026-11-06) — §8.17: `vector.sumsq(xs, axes)` sums along axes, as `vector.apply` carries a matrix along them: for each pattern of the named axes it answers the value Python's `math.fsum` gives over the squared real and imaginary parts of every element whose index has that pattern, a `list` of 2ᵏ sums, defined by a Python function stated in full. It costs one operation, builds the 2ᵏ sums alone, and refuses an axis out of range or named twice as `vector.apply` does and a square or a sum past the range of a real as `arithmetic_overflow`, where Python answers an infinity or raises `OverflowError`. - **rev 110** (2026-11-05) — §8.15: `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; a display of literal keys is refused where the unit is compiled. It was a parse_error that `**` spreads a collection the call does not read. - **rev 109** (2026-11-05) — §8.15: 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, as the names do: `s.add("a", **opts)` after `s = {1}` is `set.add() takes no keyword arguments` with `opts` holding `x`, `keywords must be strings` with a key that is no string, and with `opts` empty the refusal a name's one type gives, where the unit refused it at compile whatever `opts` held. At `list.append`, `list.insert`, `list.extend`, a set's `update` and a dict's `update` and `setdefault`. The statement refuses either way, so the receiver keeps its type; a dict's `update` writes nothing before it refuses. - **rev 108** (2026-11-05) — §8.15: `slice` called beside a `**` refuses any name before it reads a key, as `math.log` does, so `slice(1, **m)` with `m` holding a key that is no string is `slice() takes no keyword arguments`, where it was `keywords must be strings`; the same where the mapping is written, `slice(1, **{1: 2})`, and where a count too long or a `*` stands beside it. - **rev 107** (2026-11-05) — §8.15: beside a `**` before a dict whose keys are known only as the program runs, every mapping is merged before a key that is no string is refused, as Python merges a call's keywords: a name given twice is refused first, at `print`, a builtin, `sorted`, `min`, `max`, a built-in value's method and a list's `sort`, so `print(1, **m, **n, **k)` with `m` holding `1` and `n` and `k` both `x` is `print() got multiple values for keyword argument 'x'`, where it was `keywords must be strings`. A name written beside such a `**` that `sorted`, `min`, `max` or a list's `sort` does not take waits for the merge too: `sorted(xs, x=1, **opts)` is `sorted() got multiple values for keyword argument 'x'` where `opts` holds `x`, `keywords must be strings` where it holds a key that is no string, and `'x' is an invalid keyword argument for sort()` where it is empty. It was a type_error where the unit is compiled, and still is without such a `**`. - **rev 106** (2026-11-05) — §8.15: a method reached through its type's name, given only a `**` before a dict whose keys are known only as the program runs and no positional argument, is read as a call with no receiver, as Python reads it: the names are merged, a key that is no string is `keywords must be strings` and a name given twice `str.upper() got multiple values for keyword argument 'x'`, and then the call is `unbound method str.upper() needs an argument`, as the program runs. It was a parse_error. - **rev 105** (2026-11-05) — §8.15: a list's `sort` reached 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(*e, xs)` sorts `xs` where `e` gives nothing, `list.sort(*e, x=1)` is `unbound method list.sort() needs an argument` and not `'x' is an invalid keyword argument for sort()`, and with a list given `list.sort(*ys, xs)` is `sort() takes no positional arguments`, as the program runs where a `*` is typed. It counted and named what stood after a `*` as written, whatever the `*` gave. A list written of another type is refused for its type before what follows it is counted or named: `list.sort(t, 1)` over a tuple `t` is `descriptor 'sort' for 'list' objects doesn't apply to a 'tuple' object`. It was `sort() takes no positional arguments`. - **rev 104** (2026-11-05) — §8.15: beside a `**` before any dict whose keys are known only as the program runs, an argument a builtin, a function of a built-in namespace, a built-in value's method or a change that takes no name does not take, of a type or a value it refuses, is refused as the program runs once the names are merged and none is given, in the words the call has without such a `**`, after the names in Python's words: with `opts` holding `x`, `len(5, **opts)` is `len() takes no keyword arguments`, and with `opts` empty `object of type 'int' has no len()`. So is `isinstance` given a second argument that names no class, and a dict's `get` or `setdefault` of a key alone, which has no `None` to answer. 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, `v = len(5, **opts)`, and stays a type_error where the unit is compiled where what the callee answers turns on that argument or is what no value here holds, `v = abs("a", **opts)`. - **rev 103** (2026-11-05) — §8.14, §8.15, §8.20: beside a `**` before a dict whose keys only the program knows, a change read as a value and bound to no name is made, its names refused first as at its statement, and the value is then refused as the program runs: with `opts` empty, `print(xs.append(2, **opts))` appends `2` and is refused, and with `opts` holding `x` it is `list.append() takes no keyword arguments`. So is a special name whose answer no value here holds, `print((1).__add__("a", **opts))`, and `__setitem__`, `__delitem__` or an in-place name read as a value, which makes its change first. Each was a type_error where the unit is compiled, and stays one where the value is bound, or without such a `**`. §8.20: a special name beside a `*` or such a `**` is read as at any method, in Python's words for a call that unpacks: the names merged and refused, by a slot's method without reading a key, and then the count, `bool.__floor__() takes no arguments (1 given)`. Beside a `*` it was refused as the program ran in words that were not Python's, and beside such a `**` it was a parse_error. A `**` display's names at a special name are refused in Python's order and words, `bool.__floor__() takes no keyword arguments`. - **rev 102** (2026-11-04) — §8.15: beside a `**` before any dict 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, since a bare assignment binds its name to the initializer's static type (§6.4): `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)`, and, where nothing types what a `*` gives, `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. This states what rev 101 already did. - **rev 101** (2026-11-04) — §8.15: beside a `**` before any dict 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, since Python refuses a name first: with `opts` empty, `len(xs, 2, **opts)` is `len() takes exactly one argument (2 given)`, and with `opts` holding `x`, `len() takes no keyword arguments`. It was a type_error where the unit was compiled, whatever the mapping gave, and stays one without such a `**`. - **rev 100** (2026-11-04) — §8.15: beside a `**` before any dict whose keys are known only as the program runs, a receiver written of another type than the type its method is reached through is refused once the names are merged, as Python's descriptor refuses it: `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`, as the program runs, before the method reads a name or counts what follows; and at a change a name written beside such a `**` is merged with what it gives, so that `xs.append(5, x=1, **opts)` is `list.append() got multiple values for keyword argument 'x'` where `opts` holds `x`. 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) — §8.15: beside a `**` before any dict whose keys are known only as the program runs, a method reached through its type's name, where its receiver is the first thing a `*` gives, merges the names before it takes the receiver, as Python does: a key that is no string is `keywords must be strings` and a name given twice is `str.upper() got multiple values for keyword argument 'x'`, and only then is the call `unbound method str.upper() needs an argument`, or, where what the `*` gives first is of another type, `descriptor 'upper' for 'str' objects doesn't apply to a 'int' object`, each as the program runs. It took the receiver first, except at `list.sort`, which merged the names first where the `*` gave no list and refused a value of another type where the unit is compiled. So it is where nothing types what the `*` gives, as with `xs = []`, which was a type_error where the unit is compiled and still is without such a `**`. - **rev 98** (2026-11-04) — §8.15: a `**` before any dict beside a `*` whose length is known only as the program runs is read at `list.sort` where the list sorted is the first thing the `*` gives, the names merged before the list is taken from the `*`. It was a parse_error. §8.17: says why `vector` is reserved though Python has no module of that name. - **rev 97** (2026-11-04) — §8.15: a `**` before any dict beside a `*` whose length is known only as the program runs is read at a list's `sort`, which counts what the `*` gives as arguments by position once the names are merged and refuses any, and at a dict's `update`, which writes what the `*` gives and then the names, as keys. It was a parse_error at a list's `sort` and a type_error at a dict's `update`, and stays a parse_error at `list.sort` where the list is the first thing the `*` gives. - **rev 96** (2026-11-04) — §8.15: 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, `split`, `rsplit`, `splitlines` and `expandtabs`, and at `math.isclose`, in Argument Clinic's order. It was a parse_error, and stays one at a list's `sort`. `math.isclose` takes `a` and `b` by name beside a `*`, placed at the count the `*` gives, where `math.isclose(*xs, b=1.0)` was a parse_error. - **rev 95** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, beside a `*` whose length is known only as the program runs, is read at `sorted`, `min` and `max`, which count the `*` first, as Python's do: `sorted` takes one argument and `min` and `max` at least one, whatever the mapping gives; then the names, then a `default` beside several arguments, then a `key` that is no function. It was a parse_error, and stays one at `math.isclose` and a method that takes names. - **rev 94** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, 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, in Python's order: too many arguments in all, then a name missing or given by name and by position, then the first name the callee does not take, then what the call written out with that count refuses. 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) — §8.15: 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: a builtin, a function of a built-in namespace, a built-in value's method or a change. Python's order holds: every argument, then the receiver where the `*` gives it, then the names, any of which is refused, then the count. It was a parse_error, and stays one where what is called takes names. A `bool`'s method that `int` defines is named for `bool` at a call that unpacks, `bool.bit_length() takes no keyword arguments`, where it was named for `int`; and at a change an operand that is no dict is refused before a name written beside it. - **rev 92** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, at a function of a built-in namespace is read as Python reads it: `math.isclose` takes its names as Python's does, named alone, and any other function refuses any name, `math.log` before it reads a key; a name given twice names the function with the module Python defines it in. It was a parse_error. A namespace's function is named so where a keyword is written too: `struct`'s are `_struct.pack()` and the like, where they were `struct.pack()`. - **rev 91** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, at a method that changes a collection is read as Python reads it: 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 change refuses any name before the collection changes, a count it does not take refused only where the mapping gives no name. It was a parse_error. A call that none of the 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; it was a type_error where the unit is compiled, in the words for a mapping that gives none, which `enumerate(**opts)` given `start` alone contradicted. - **rev 90** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, at a built-in value's method called as a value, and through its type, is read as Python reads it. `split`, `rsplit`, `splitlines` and `expandtabs` read the names as Python's do, their receiver not counted, and name themselves alone; any other method refuses any name, named with its type or, where Python's call refuses a name before it reads one, alone, `startswith() takes no keyword arguments`. It was a parse_error. A method that changes its receiver does not read one yet. - **rev 89** (2026-11-04) — §8.15: 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, which are counted as written, and `sorted` is refused past the merge under `sort`'s name, as Python's is. A `key` the mapping gives is never a function, and is refused `'int' object is not callable` once there is an element to call it on. It was a parse_error. The sentence saying a `**` beside a built-in function's `*` is a parse_error now names the one call that reads both, `print`. - **rev 88** (2026-11-04) — §8.13: `enumerate` reads its names where they stand, as Python's does: with two arguments in all one name must be `start`, and with one it must be `iterable`, so `enumerate(start=1)` is `'start' is an invalid keyword argument for enumerate()`. It was told it lacked its iterable. The sentence quoting that refusal with `(pos 1)` was wrong for a call with more names, which Python and this language answer without it. §8.15: a `**` before any dict, such as a name that holds one, at `enumerate` and `sum` gives the call its names as if written, `enumerate`'s read where they stand. It was a parse_error. A call that none of the names its mapping could give would let run is a type_error where the unit is compiled, in the words it meets when the mapping gives none. - **rev 87** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, at a builtin that takes names (`int`, `round`, `pow`, `complex`, `str`, `zip`) gives the call its names as if written, and at `dict` its keys, in a new mapping; where a name the callee does not take is among them, Python's order holds: too many arguments in all, then a name missing or given by name and by position, then the first such name. Otherwise the call refuses what the call written out does, as the program runs. A call whose type turns on the names given is a type_error where the unit is compiled. It was a parse_error. - **rev 86** (2026-11-04) — §8.15: a `**` before any dict, such as a name that holds one, at a builtin that takes no name is read after the positional arguments, as Python reads it: a key that is no string or a name given twice is refused as it is met, then any name at all, before the arguments are counted, each a catchable `type_error`; a mapping that gives nothing leaves the call as written. It was a parse_error. - **rev 85** (2026-11-04) — §4.3, §8.15: a `**` at `print` before any dict, such as a name that holds one, gives its keys as keywords when the line is composed, merged and checked as Python does: a key that is no string or a name given twice first, then the first name `print` does not take, written or given, then `sep` before `end`, each a catchable `type_error`. It was a parse_error. A name written beside such a `**` that `print` does not take is refused after the merge, where it was refused as the unit compiled. - **rev 83** (2026-11-03) — §7.1, §8.13, §8.16, §8.17, §8.18: a count a built-in function or a namespace function does not take is a type_error at its name in Python's words, before any argument is read: `len() takes exactly one argument (2 given)`, `math.log requires 1 to 2 arguments`, `missing format argument`, and a count of names in Argument Clinic's, `round() takes at most 2 keyword arguments (3 given)` where nothing is given by position. A namespace function's count was a parse_error. - **rev 82** (2026-11-03) — §8.6, §8.7: a keyword `print`, `sorted` or a list's `sort` does not take is a type_error in Python's words, the first one written, `'x' is an invalid keyword argument for print()`, and named for `sort()` from `sorted`, as Python names it; at a list's `sort` it is refused after the positional arguments are counted. - **rev 81** (2026-11-03) — §8.15: a `*` whose length is known only as the program runs reads beside a name at a built-in type's method, a `dict`'s `update` and a list's `sort`, as in `"a b c".split(*ys, maxsplit=1)` and `xs.sort(*ys, reverse=True)`: the name binds as Python binds it once the count is known, and a count that does not fit it is a catchable refusal in Python's words as the program runs, counting the name with the positional arguments. At `sort` the spread and `reverse`'s value are read before the count is checked, and through `list.sort` the first thing the spread gives is the list sorted. - **rev 79** (2026-11-03) — §8.15: a `**` before a dict display whose keys are all literals gives its keys as keyword arguments to `print`, a builtin, a built-in type's method, `sorted`, a list's `sort` and a built-in namespace's function, as Python binds `f(**{"k": v})` as `f(k=v)`; a call it does not fit is a type_error in Python's words and order. §4.3: `print`'s `file` may be given `None`, its default. A `math` function's keyword given twice, by name and position, or not taken, is told in Python's words. - **rev 78** (2026-11-03) — §13.8: a read through a family of classes runs as Python's does until it reaches a class whose method returns nothing, wherever it is written: in a method a subclass inherits, in a script written above the class and in a comprehension. A call whose arguments the method of the class it reaches does not take is refused as it reaches that class, in the words of Python's `TypeError`, and `except refuse` takes it. A read no class can answer, and a call no class takes, stay refused when the program compiles. - **rev 77** (2026-11-03) — §8.3: `pow` with a modulus given an operand that is not an integer is refused in Python's words for the class whose power Python asks first. §8.4, §8.14: `xs += c` on a list takes any finite collection, as `extend` does. §8.16: `math.floor` and `math.ceil`. §8.20: a `real`'s `__floor__` and `__ceil__` answer as the two do. - **rev 76** (2026-11-03) — §8.20: a special name a built-in value holds, called by name, answers as the spelling it stands for, a reflected one with its operands the other way round, and one the value does not hold is told so in Python's words. Where Python answers `NotImplemented`, a method, a class or the machinery behind a value, the call is a type_error saying so. `__setitem__`, `__delitem__` and the in-place names are statements, as the changes they stand for are. - **rev 75** (2026-11-03) — §7.1.1: a fold over `real` values is exactly rounded where it adds: `sum` is `math.fsum`'s value, `avg` `statistics.fmean`'s, `rms` the root of `statistics.fmean` over the squares and `geomean` `statistics.geometric_mean`'s; the folds that select or interpolate answer what the same formula answers over a list, and the rest can differ from a Python computation over a list in the last place; `covariance` is the population figure. §8.3: `sum` over a list adds left to right, as CPython 3.11's does, beside the store fold. §5.1: why an iterator or a values view is not a key or a set element. - **rev 74** (2026-11-03) — §5.3, §6.2: an argument of a type a string's method does not take, a list's or a tuple's `index` bound, a list's `insert` position and a slice's `indices` argument are refused in CPython's sentence, which differs by method as CPython's does, where the argument is of a class CPython has, and `join` over what is no iterable of strings names the first position that holds none; a sequence or mapping pattern that can never match, as one alternative of several, is left out of its arm, which stands on the others, where it was refused. - **rev 73** (2026-11-03) — §3.5.12, §3.5.13, §8: a set literal, a dict literal's keys and a key written into a dict that would hold a tuple beside a scalar, or tuples whose positions do not join, are refused naming the two types, as a list literal's elements are, since a `value` holds a scalar. §8 opens with how an operator given operands it does not take is refused: in CPython's sentence, naming CPython's classes, where CPython refuses the same operands, and naming the operand types the operator takes where an operand is of a type CPython has no class for. - **rev 72** (2026-11-03) — §8.6, §8.9, §8.14: 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 element before the next, as CPython's do, so one over the collection being changed is refused at its next read in CPython's words and what was written before stays; a dict's pair of the wrong length is named by its position after the pairs before it are written. §8.9 names a list's `extend` and the in-place set and dict methods among the calls that read a generator expression, and §8.14 states a list's `extend` over the list itself, which stops at the length it began with, as a kept divergence. - **rev 71** (2026-11-02) — §5.1, §8.3, §8.6: `|`, `^`, `union` and `symmetric_difference` across scalar element types build a `set`, as CPython builds a mixed set, and a `set` takes another scalar in place; a keys or items view takes any collection beside it on either side of `&`, `|`, `-` and `^`, an intersection keeping of two equal numbers the one CPython keeps, and a number beside a view is refused in CPython's words. - **rev 70** (2026-11-02) — §13.8: 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 the method as CPython does; no `except` takes the refusal. A read reaching only classes that return a value answers as before. - **rev 69** (2026-11-02) — §5.6, §13.9: 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 class. §6.2: an arm below arms naming classes reads the scrutinee at what their tests failing leave it, and a guard narrows a name its own pattern binds. §8.10: two scalars that are not both numbers join to a `value` holding their classes, where an `int` beside a `string` was a type_error. - **rev 68** (2026-11-02) — §5.6, §13.9: a name holding a `value` is narrowed by an `isinstance` test against types, in the branch it takes, after it in an `and`, after a comprehension's `if` and in a conditional's first branch, and an `or` of two tests narrows to what they join to. §6.2: a `match` over a `value` whose class arms all write Python's parentheses is Python's, every arm a test and none required, `case int(n):` and a capture binding the value, an arm naming classes narrows a scrutinee written as a name, and a guard narrows its arm's body. §8.10: a scalar beside a `value` joins to the `value`. - **rev 67** (2026-11-02) — §5.1, §5.3, §8.11, §8.13: a tuple of scalars, or of class instances, iterates, converts and is indexed and sliced at a computed position as a list of its elements is; only one holding a collection beside a value of another type is not. `dict(x)` reads any finite collection of pairs as `update` does, a fault in a pair raised as it runs. §5.3: an index of a type Python has that is no `int` is told in Python's words. §13.1: a field declared twice keeps the type written last, and a computed field is still declared once. - **rev 66** (2026-11-02) — §6.2: a `match` over a value Python has is Python's: every arm a test, tried in the order written, and none need run. It reads any scrutinee, a shape arm needs no catch-all, and an arm that cannot match compiles and never runs. Class patterns (`case int():`, `case C():`, `case m.C():`) test as `isinstance` tests, at an arm's head or inside another pattern, a type that reads itself holds one pattern, and past that the test raises Python's `type_error`. A class named with no `()`, over a value no class match follows, is told it captures in Python. §6.9: a `match` definitely returns only if an arm takes every value. §9.2.3, §13.5: `case ():` in a class match is a test, and only arms with no `()` are exhaustive. - **rev 65** (2026-11-02) — §3.5.4, §4.2, §4.3, §6.8: a script with no `-> type` whose body holds a `return ` is a value script of the type its `return`s give. §6.9: a suite definitely returns if any statement of it does, and the rule names `raise` and `try`. §8.8: the name of a `def`, a method or a module read with no call is told no value holds it. §9.7: a chain of calls below going more than 1000 levels is a type_error. §13.4: so a method's. - **rev 64** (2026-11-02) — §3.4: a name nothing binds is told in Python's words. §8.14: a method a value does not hold is told in Python's words, and a change read as a value says it answers `None`. - **rev 63** (2026-11-02) — §4.2, §4.3: a call that does not fit is told in Python's words and order, a method's count holding its receiver, and one no type is known for only as it runs. §8.8: a call of a value is a type_error in Python's words, or told as it runs, and no longer a parse_error. §8.14: an element write or `del` on what takes neither is told in Python's words. §13.9: 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 (§11.1). 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 the current revision. The history starts at rev 1. - **rev 62** (2026-11-01) — §13.1: a class exists from the line that declares it, a body may name one declared anywhere in the unit, and a line that runs before it is told `name 'K' is not defined`. §6.4: a type may be written as a string, and a construction alone is a statement. §13.1: a method may read a field declared below it. - **rev 61** (2026-11-01) — §4.1, §6.8: a declaration may call one above or below it in its unit, the unit compiling a callee first, and only a call leading back to its caller is refused; a module's own statement reaching a `def` below it is told `name 'g' is not defined`. §13.1, §13.3: a method called or a field read through a value the compiler does not follow runs against the instance's own class, and a class holding no such method raises in Python's words. §13.4: a value method's body holds any statement, and a method may call one written below it. §13.8: a method call through classes sharing no base runs the instance's own. - **rev 60** (2026-11-01) — §5.1: what no dict or set could hold is told in Python's words, `unhashable type: 'list'`, when it is written and as the program runs when it is looked up. §8.6: `any`, `all` and a set's `intersection`, `issuperset` and `isdisjoint` stop reading an iterator where CPython's do. - **rev 59** (2026-11-01) — §8.15: a `*` at a built-in method's call, through a type's name the receiver too, and a count a built-in method does not take in Python's words. §8.6, §8.14: a set's `union`, `intersection`, `difference` and `update` take any number of collections, with `difference_update`, `intersection_update` and `symmetric_difference_update` beside them, `d.update()` changes nothing, and `sort`'s positional argument raises as Python's does. §8.9: a generator expression right after a `*`. - **rev 58** (2026-11-01) — §3.5.10, §8.13: a format spec's sign, `z`, `#` and grouping format as Python's do, and a collection refuses them. §8.6: a method through its type's name, `str.upper("a")`, is the method on its first argument. §8.3, §8.14: a dict's `|=` and `update` read any collection of pairs. §8.15: a `**` at a script's or a method's call gives each entry as a keyword argument. - **rev 57** (2026-11-01) — §3.5.10, §5.3: a string's `.format` and `.format_map` read and answer as Python's do, and a template's rules are stated. §3.5.12, §8.3: a dict's `**`, `|` and `|=` merge. §4.3: `raise` holds several arguments. §5.3: `translate` takes a list, a tuple or a string as its table. §8.3: `%` with a string on its left formats as Python's does, and `@` and `@=` read and are refused naming their operands. §8.6: a type's own three methods are reached through a value. §10.1: `@` left the forms the lexer refuses. - **rev 56** (2026-11-01) — §8.19: a named expression (`:=`) reads, where Python's grammar reads one, and binds when it is evaluated. §10.1 no longer says it is refused. §4.3: `print`'s `sep` and `end` are any expression that gives a string, read after the arguments in the order written, `None` as the whole of either is its default, and `flush` is read and changes nothing. A condition of `assert` may begin with a bracket, and §4.3 says when the verb is read instead. - **rev 55** (2026-11-01) — §5.3, §8.6: `s.translate(table)` reads. `str.maketrans`, `dict.fromkeys` and `float.fromhex` read, called on the type's own name, as a statement and as a `key=`. `.format_map` is told what `.format` is. - **rev 54** (2026-11-01) — §10.1: what reaches the limit on the work of reading. Reading a call nested in a call no longer doubles at each level, so 20 method calls nested in one another read like any others. What reaches the limit now is 70 dicts unpacked one inside another beside a syntax error. - **rev 53** (2026-11-01) — §8.6: a `real` takes `is_integer` and `hex`, an `int` or a `bool` takes `bit_length` and `bit_count`, and each takes `as_integer_ratio`. §5.2: an `int` and a `bool` have `.numerator` and `.denominator`. - **rev 52** (2026-11-01) — §10.1: reading a program has a limit in work, counted and so the same on every host. Past it, a program Python refuses gets Python's answer, and any other is a parse_error at line 0. - **rev 51** (2026-11-01) — §8.9: a comprehension's `if` filters are each asked only for a truth value, so their types need not join. §10.1: how long a chain reads, the count of `elif` a chain holds, and where other nesting stops. - **rev 50** (2026-10-31) — §8.11: a `string`, a `set`, a `dict` (its keys) and a dict's view unpack into a target list, in an assignment, a `for` and a comprehension clause, where each was a type_error; in a comprehension clause a group in a fixed tuple's slot reads an iterator, a view, a `set` or a `dict` where it stands. What stays a type_error is stated. - **rev 49** (2026-10-31) — §8.11: a comprehension clause checks a starred group's width over what its star absorbs, in Python's words, where it told the list's count. - **rev 48** (2026-10-31) — §4.4: a comprehension's later source or `if` that reads a name a later clause binds is a type_error, whatever an outer scope holds under the name, where it read the outer value; the words are Python's, a free variable's from a comprehension inside it, and a module called is `'module' object is not callable`. §8.3: `key=` takes a method of a built-in type, `key=str.lower`, which reverses the sentence that called it a parse_error, and a key over what is no collection is told in Python's words. §8.15: what a spread's compile refusal gives up, and where a `**` is told. §10.1: where the reading of what Python's compiler refuses stops short. - **rev 47** (2026-10-31) — §3.4: a name is read in Normalization Form KC, as Python reads it, so every spelling of one form is one name, where any other form than NFC was refused; a keyword is compared as written, a name that keeps a combining mark with no letter to compose into may name a local and nothing a store holds, and a string is not read. §6.1: a value's refusal for a combining mark no longer calls it a literal spelling. §10.1: a spelling NFC would change is no longer a form the lexer refuses, and a position CPython's compiler reports counts bytes on the English surface. Appendix A: the same. - **rev 46** (2026-10-31) — §4.3: a `return` outside a script is refused where it ended the program, and `from __future__ import` is read as Python reads it. §4.4: `__debug__` reads as `True` and cannot be bound. §10.1: what Python's compiler refuses is answered at its place and in its order, ahead of every refusal but a grammar error, where a decision before the call stood. - **rev 45** (2026-10-31) — §8.15: a keyword argument beside a built-in function's `*` binds as Python binds it, where it was refused, and a count that does not fit the keyword is refused in the words of the `TypeError` Python raises. A `*` alone among the positional arguments is read after the keyword arguments' values, and a collection with no element type yet holds nothing. - **rev 44** (2026-10-31) — §4.4: a name a body binds anywhere is that body's from its first line, a comprehension's variable takes a built-in's name through the comprehension but for its first source, and a capture, an import or a handler's name takes one as an assignment does. A class the unit has declared holds its name ahead of a built-in. - **rev 43** (2026-10-31) — §4.4: a name the program binds now comes before a built-in, `print` or a verb of that name, where a built-in's name always won. §8.15: a `*` before a built-in function's argument spreads the collection, checked as the call written out at each count it could give. §8.6: `zip()` gives nothing. §8.13: `format(value)` takes the empty spec. - **rev 42** (2026-10-31) — §4.2: 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) — §4.2: a parameter list reads a `/` and a bare `*` as Python's do, a parameter may follow a variadic, and the listing writes a list as it was written. §4.3: `raise` alone raises what is being handled, `raise e` raises what `e` caught, and `from` takes `None` or a caught exception. §13.4: a method's arguments bind as a script's do. - **rev 40** (2026-10-31) — §3.5.1: a backslash reads as CPython's: octal digits name a codepoint, a backslash and a line feed are nothing, and before any other character both are kept. §4.1, §4.3, §6.4: an expression alone is a statement whose value nothing keeps, a builtin there is one, `...` alone does nothing, and an annotation with no value binds nothing. §4.2, §5.5, §13.7: `-> None` says what no annotation says, `str` and `float` are read, and a collection type takes either bracket. §4.3: an import may list modules and take `as`. §4.4: prefix `+` is an operator. §13.1: a class with no base may declare nothing. §10.1: those forms are no longer refused. - **rev 39** (2026-10-31) — §3.5.1: a string opens with either quote, a prefix `u` or `U` changes nothing, an f-string may be triple-quoted, and its field may hold a string in the other quote and ask for `!a`. §3.6, §4.3: a `;` ends a simple statement, and a block's simple statements may stand on its header's line. §3.7: the reserved words are Python's keywords and `null`; this language's own words are names wherever their form does not stand. §8.7: a printed collection of strings reads back as written. §10.1: those forms are no longer refused. - **rev 38** (2026-10-31) — §10.1: what a source ending in a carriage-return / line-feed pair answers at its end, and the line a dedent on a continued line is answered past. - **rev 37** (2026-10-29) — §3.5.1, §3.5.5, §3.5.6, §3.5.8, §3.5.14: a typed value has one spelling, its factory call. §3.5.14: a numeral with no exponent or with an all-zero mantissa is a parse_error, and a magnitude too large to represent is refused at write. §3.8: a typed value has no token class of its own. §5.2, §14.4: a written window is printed back as factory calls, `valid_from=datetime("…")`. §6.2: `case datetime("…"):` reads as a class pattern and is refused. §7.1, §7.1.2, §7.5: refusal codes are snake_case, `derived_field`, `not_recorded`, `limit_exceeded`, `scope_forbidden`, `asserted_field` and `already_in_effect` among them. - **rev 36** (2026-10-29) — §3.7: `del`, `from`, `import` and `yield` are reserved words, as they are in Python, so every keyword Python reserves is reserved. §4.3: `del` is a reserved word. Appendix B: `import` is a reserved word. - **rev 35** (2026-10-29) — §3.7: `async`, `await`, `elif`, `global`, `is`, `nonlocal`, `pass`, `raise` and `while` are reserved words, as they are in Python, and of Python's keywords only `del`, `from`, `import` and `yield` are not. §4.3: `pass` and `raise` name nothing. §8.1: `is` names nothing. §8.8: `await` and `async` name nothing. §10.1: a line a `while` or an `elif` begins is the header Python reads, before a `(` too. - **rev 34** (2026-10-29) — §3.7: `lambda` is a reserved word, as it is in Python, and a `lambda` that begins a path is read as a lambda. §8.8: where the word begins no lambda Python reads, the parse_error is where Python answers it. - **rev 33** (2026-10-23) — §3.5: each prefix literal spelling denotes the same literal as its factory call. §3.9: the natural language is set by the `language` parameter alone. A name is bound by bare assignment. Compiling a module, forgetting a script and reading a script's metadata are wire operations, named in the [MCP Reference §9.2](/ref/dke/mcp/#scripts-address-by-module.script). A constructed value is evaluated as the program runs and is never stored. `result_type_set` sorts by type name. The `DerivedField` condition names the rule whose text at that moment the store holds no record of. Appendix E describes this document from rev 1. - **rev 32** (2026-10-21) — §14.2: `absent(...)`, `not_asserted(...)` and `known_absent(...)` take a pair, `absent(Gap.x.y)`, in the grammar and in a new **Over a pair** item; §14.5 says what a pair ranges and that a rule testing an absence over a pair may not conclude a field of its kind. - **rev 30** (2026-10-17) — §13.8 says constructor chaining is not part of the language, and that construction takes only the instance name and runs no code. - **rev 29** (2026-10-17) — §7.1 says that compiling a module again removes what its text no longer declares, that this is a removal, and that a `read-write` key's compile that would remove is refused with `ScopeForbidden`, naming what would go and leaving the store as it was. - **rev 28** (2026-10-17) — §7.1 says a value `restore` brings back is in force as a new claim at the current instant, the word this document uses for a point in time. - **rev 26** (2026-10-17) — The header states Version, Status, Audience and the Markdown copy, one field to a line. §1.3 and §11.1 state the version with its revision, `alpha · API 1 · rev N`. §7.5, Write-verb semantics, is a heading of its own again; it had run into the last paragraph of §7.4. Citations of the MCP Reference and the Python Reference name them so and link to the section they cite. `written_by` and `writers` (§5.2, §7.2) name store keys. §8.3's example of a power that refuses at run time is `x ** 0.5` with `x` a negative `real`; the literal `(-8) ** 0.5` computes, as the same paragraph says. Struck-through passages and the amendment notes of §10.4 are removed, and the page states only what holds. - **rev 21** (2026-10-14) — This appendix records revisions 13 to 20 as one entry. Nothing else changed. - **rev 13 to rev 20** (2026-10-13 to 2026-10-14) — every revision in this range but rev 16: new sections and examples in §3, §4, §5, §6, §8, §10 and Appendices A and C, 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 (§8.3). - **rev 12** (2026-10-12) — §8.7: a `*` after `print` or `exec` written without its parentheses is read as a sign is; a starred or awaited value among its arguments is read as any other; the values after a list that is its first argument are read as the call's; and what Python raises inside a call that trails a first argument its rule for a forgotten comma read no value of stands, inside a call's trailer or a subscript's. §8.10 and §8.14: a condition with no `else`, or a `del` target, that ends in a call whose arguments do not parse is weighed as the value before the call; an `=` that begins a call's argument names no keyword. §8.11 and §10.1: a starred value alone in parentheses among the targets is taught as Python teaches it; the bracket a `:=`'s value opens and the source never closes is the answer, a bracket the value closes is read to its close, and a call it never closes whose reading stops on its line is backed off; a call with its arguments out of order side by side after a value among a statement's elements is answered at the first of them that assigns. - **rev 11** (2026-10-12) — §8.8: a call of `None` is read as a call of any other value is, its arguments and what follows them. §10.1: an `elif` with no `if` above it is a grammar error at its word, ahead of anything in its line and of a grammar error below it bar a string not terminated or a number refused; a `while` or an `elif` line no statement reading the word as a name takes, a form this language does not write among them, is the header Python reads, with no `:` after a call as with one; an `await` among a statement's targets is named as the value it is, and a starred value alone in parentheses among them is answered at its star; a group holding an `=` reads as no dict key; a call whose arguments stand out of order among an assignment's or a `del`'s targets is answered as in any call; a script's call the line goes on past reads what follows it as a trailer; a bracket never closed is the answer past a `:=` whose value runs into it; and the words excepting a header's body from the rule for a word after a bracket never closed are struck, a body being read as the statement it is. - **rev 10** (2026-10-12) — §8.7: a decorator's `print` or `exec` written without its parentheses is that error over a later grammar error, and a character no token begins with does not stand in its place. §8.8: the arguments of a call of a value, and what follows them, are read as CPython reads a call's, and the bracket answered where that reading reaches the end. §8.9: a generator beside another argument gives way to the call's bracket never closed where CPython reads the arguments to the end, and an empty place after a keyword argument is answered at its comma. §10.1: a `:` after an expression that no annotation follows is answered at the `:`; a call of `print` the line goes on past keeps no value; and a word Python begins a statement with, after a bracket never closed on its line, is the answer. - **rev 9** (2026-10-12) — §10.1: a `while` or an `elif` before a `(`, which is read as a call, is the header Python reads where a `:` stands after the call at the line's own depth, and is refused at its first word; a grammar error Python's rules for an invalid value raise inside the value of an `=` where Python reads a named expression is the answer, before the `=` is weighed; and that value is read to the primary before a trailer the source never closes, where Python's reading of the trailer stops on its line. - **rev 8** (2026-10-12) — §4.3: a comma, a `:` or a `**` in a `lambda`'s header is no part of what its bracket holds, so a group holding the lambda is named for it, and a `{` holding one alone is a set display. §8.8: a `lambda` where Python reads none, as an operator's operand, is answered at the word with no header too. §10.1: a `while`'s condition is read as a named expression, as an `if`'s is; an `=` whose operand does not parse as Python's parser reads it is answered at the `=`; and a `True`, `False` or `None` before a comma at a statement's head is read as the value it is, so a grammar error later on the line is reached. - **rev 7** (2026-10-12) — §8.7: a `print` or `exec` that CPython reads as a call missing its parentheses is answered at the word over a grammar error on a later line, bar an error CPython raises on its first reading — a bracket never closed, a string not terminated, a number its tokenizer refuses, an error in an f-string's field, a dict's own error at a key that reads — and bar a word among the targets a `del`, a `for` or an `as` names. Inside a bracket an `=` after the word's values makes no target of them, a keyword argument's name included. A first argument's call whose arguments stand out of CPython's order is read to the first out of place, and the word is the answer. §10.1: in a dict, a call whose arguments stand out of CPython's order is read only to its name, and a key with no `:` after it is answered there. - **rev 6** (2026-10-12) — §8.7: a `print` or `exec` before a `[`, on a line a grammar error fails past the word, is a call missing its parentheses, answered at the word, where the `[` opens a list that reads and stands among no targets before an `=`. A grammar error raised among the values read after the word is the answer instead, and inside a bracket a value straight after the list is a forgotten comma at the list. An `await` and its value after the word are its first argument. §8.8: after such a `print`, the call is the answer over an `await`. - **rev 5** (2026-10-11) — §7.1: a key scoped `read-add` reads and writes only what adds to the store. An `update` over a value its own source holds is refused when it runs, with `ScopeForbidden`, and the program ends there, as does a commit that would replace a value the store holds. Removal, the marking verbs, restoring or dropping a checkpoint, and compiling over a module the store holds are refused before any statement runs. - **rev 4** (2026-10-11) — The document names itself by its title where it said "DKE Python Spec", and its closing line states no release, the Version line being where the release is stated. §1: the MIT grant is spelled licence. Cross-references take the published documents' short names, the ones the footer uses — Python Reference, MCP Reference. Nothing else changed. - **rev 3** (2026-10-11) — §1.3, §11.1 and this appendix: the revision is the surface's, one number shared by every published document, the web pages and the service wire, moving whenever any document changes; this appendix records the revisions at which this document changed. The Version line moves from rev 1 to it. Nothing else changed. - **rev 1** (2026-10-11) — The document states its revision: the Version line carries `rev N`, §1.3 names it, §11.1 says what it promises and where a program reads it, and this appendix is added. §10.1: the arguments of a `print` or `exec` written without parentheses are read as CPython reads them — a `-` or `+` after the word begins an expression, and where a value stands beside it outside a bracket the word is the call; inside a bracket a value beside an argument is answered at the argument; a run of names before the word is read through to the word, which is the call; a later argument that runs into a bracket never closed is that bracket; nothing is read on after a first argument that is a conditional or a `lambda` unless a string or a sign follows the word. The `del` passage: the targets are read to a bracket never closed before any is weighed. The never-closed paragraph: a decision met inside an argument gives way to the bracket. --- **End of the DKE Python — Language Specification.**