mpl/conformance/JUDGMENT_CALLS.md
2026-07-09 23:25:01 -04:00

14 KiB
Raw Permalink Blame History

JUDGMENT_CALLS — semantic decisions awaiting ratification

RATIFIED 2026-07-09 — this file is the semantic record of MPL M0.

Every section below is a semantic question Stage 2 surfaced, stated with the behavior js/mpl.js exhibited at observation time (Stage 2 head, ea66a2a) and the corpus entries that pinned it. Each RULING: line records Reverend's ratified decision of 2026-07-09. BLESS rulings kept the observed behavior; OVERRIDE/SPLIT/IMPLEMENT/AMEND rulings changed the grammar or the interpreter in Stage 3, and the corpus was re-recorded to match. The "Observed:" text is the historical record, not the current behavior — the rulings govern.

1. Division by zero

Question: what does x ÷ 0 do — error, infinity, or ⊥? Observed: raises err_div0 (both ÷ and /, integer or float operands). Pins: 043_div_zero.

RULING: BLESS. x ÷ 0 (and /) raises err_div0. Division by zero is undefined.

2. The value of a ∀ expression

Question: what does ∀ x ∈ list : body evaluate to? Observed: the value of the last body evaluation; for an empty list. Pins: 018_forall_value, 019_forall_scope.

RULING: OVERRIDE. is an iterator; the expression's value is always (including empty collections). Using a statement as a value is undefined — same principle as ruling 3.

3. Result when no guard matches

Question: what is (false ⟹ e) with no | fallback? Observed: an internal no-match that surfaces as everywhere except directly to the left of |. Pins: 021_no_guard_match, 023_alt_chain.

RULING: BLESS (semantics, not mechanism). An unmatched guard yields unless caught by |. Implementations choose their own internal sentinel.

4. Integer vs float display

Question: is 4 ÷ 2 shown as 2 or 2.0? Are all numbers one type? Observed: all numbers are IEEE doubles; integral values display with no decimal point (2), non-integral as JS renders them (0.5, and 0.1 + 0.2 shows 0.30000000000000004). Literals normalize (0077, 0.500.5). Pins: 006_number_display, 007_float_arithmetic.

RULING: OVERRIDE. Numbers are exact rationals (arbitrary-precision integer numerator/denominator). Decimal literals convert exactly (0.1 = 1/10). Display: integers bare; otherwise lowest-terms fraction num/den, sign on the numerator, denominator > 0; zero as 0. Output is valid MPL that re-evaluates to the same value. IEEE doubles are rejected: the language must not lie about 0.1. √ vs is logged as a standing M1 decision.

5. ✎ formatting per type

Question: exactly how does render each value type? Observed: numbers via host String(); strings bare at top level but quoted inside lists; booleans true/false; lists [a, b] with one-space separation; closures as λ; as . Pins: 009_string_concat, 011_list_display, 031_show_all_types.

RULING: BLESS, spec'd. renders: numbers per ruling 4; strings bare at top level, double-quoted inside lists; booleans true/false; lists [a, b] (comma-space); closures as λ; as .

6. ⊥ display and propagation

Question: is a first-class value or a poison that propagates? Observed: a first-class value — it displays as , concatenates into strings ("x" + ⊥x⊥), sits in lists, but arithmetic on it is err_num. Pins: 030_bot, 052_bot_arith.

RULING: BLESS. is first-class: displays as , sits in lists, string concatenation renders it ("x" + ⊥x⊥); arithmetic on it is err_num.

7. Equality across types

Question: what does = mean across types and on structures? Observed: structural (JSON-serialization) equality — lists compare element-wise, cross-type comparisons are false (1 = "1"false), and ⊥ = ⊥true. Comparing a self-referential closure crashes with a keyless host error (unpinnable by the corpus). Pins: 034_cross_type_equality, 033_string_compare.

RULING: SPLIT. Data equality is structural: element-wise lists, cross-type = is false, ⊥ = ⊥ is true, 0.5 = 1/2 is true (same rational). Any equality comparison involving a function raises err_fn_eq — function equality is undecidable; a keyed error replaces the current keyless crash.

8. Ordering across types

Question: what do < > ≤ ≥ do on mixed or non-numeric operands? Observed: raw host comparison with JS coercion — 1 < "2"true, "10" < 9false, true < 2true; strings order lexicographically. Pins: 035_mixed_compare, 033_string_compare.

RULING: OVERRIDE. < > ≤ ≥ are defined on number×number and string×string (Unicode code-point order) only; anything else raises err_compare. Host coercion (1 < "2") is JavaScript soul leakage, not mathematics.

9. Closure capture

Question: do closures capture values at definition time or the environment? Observed: the environment — later mutation of a captured variable is visible on the next call (013 prints 11 then 21). Pins: 013_closure_capture, 026_def_vs_assign_scope.

RULING: BLESS. Closures capture the environment. (The corpus itself forces this: factorial only works because the λ sees its own later binding.)

10. Recursion depth

Question: what bounds recursion, and how does exceeding it surface? Observed: the host JS stack — deep recursion dies as a keyless RangeError before the 500 000-step budget can trigger, so the corpus cannot pin it (no error key). Iteration-heavy programs do hit the budget and raise err_steps. Depth ~500 is comfortably safe. Pins: 016_recursion_sum (works at 500), 053_step_budget (err_steps).

RULING: OVERRIDE. Recursion is bounded by an explicit λ-application depth counter, limit 10 000, raising err_depth. A keyless host RangeError is a hole in the error surface; the limit is conformance surface in every implementation.

11. The step budget itself

Question: is "500 000 evaluation steps, then err_steps" part of the language, and is that the right number? Observed: hard-coded 500 000; nested loops over a 10-element list six deep exceed it. Pins: 053_step_budget.

RULING: RECLASSIFY. The step budget is an environment resource limit, not language semantics — like out-of-memory. err_steps (500 000) remains in the browser implementation; entry 053 leaves the portable corpus and becomes a js/test implementation test.

12. String escape round-tripping

Question: which string escapes exist, and what does an unknown one mean? Observed: \n and \t expand; \" and \\ escape themselves; any other \x silently drops the backslash ("drop\qme"dropqme). The ANTLR grammar instead REJECTS unknown escapes — a recorded divergence. Pins: 008_string_escapes; divergence in DIVERGENCES.md ("drop\qme" and the "a\zb" fuzz entry).

RULING: OVERRIDE. Escapes are exactly \n \t \" \\; any other \x raises err_escape, matching the grammar. Silent data-mangling is forbidden.

13. Guard condition truthiness

Question: what may a guard condition be? Observed: true and non-zero numbers fire the guard; false, 0, strings, lists and do not (they yield the no-match path) — so numbers are truthy for but nothing else is. Pins: 022_guard_truthiness.

RULING: OVERRIDE. A guard condition must be boolean; any non-boolean condition (numbers included) raises err_bool. A condition is a proposition.

14. ∧ operand truth

Question: do / accept the same truthiness as ? Observed: no — operands are tested with strict boolean equality, so 1 ∧ truefalse while (1 ⟹ x) fires. Both operators short-circuit (observable by side effect). This asymmetry with #13 is the sharpest accident in the surface. Pins: 036_logic_ops, 037_logic_short_circuit.

RULING: SPLIT. short-circuit (blessed, observable by side effect) and demand boolean operands — a non-boolean evaluated operand raises err_bool instead of silently comparing false. One notion of truth, everywhere; an unevaluated right operand raises nothing.

15. Block scoping

Question: does { … } create a scope? Observed: no — bindings made inside a brace block leak out (028 prints 9). Only λ bodies and each iteration scope. Pins: 028_block_no_scope, 027_block_sequencing.

RULING: BLESS. Braces group; they do not scope. Scope is created by binders only (λ parameters, ∀ iteration variables) — the Curry-Howard reading: a discharged hypothesis IS a λ. An explicit local-binding construct (let/where) is logged as a standing M1 decision.

16. ≜ vs ← scoping and creation

Question: how do define and assign differ? Observed: always binds in the current scope; mutates the nearest enclosing binding and silently creates one in the current scope when nothing is bound (assignment-before-definition is legal). Both are expressions returning the value; both rebind freely (x ≜ 1; x ≜ 3 is legal re-definition). Pins: 024_def_assign_rebind, 025_def_assign_value, 026_def_vs_assign_scope.

RULING: OVERRIDE (both halves). introduces a name exactly once per scope — same-scope redefinition raises err_redef; shadowing in inner scopes is legal. mutates the nearest enclosing binding and raises err_unbound when none exists — silent creation is the typo trap. Both remain expressions returning the value.

17. Type constraints

Question: does λx∈: constrain anything? Observed: the constraint parses (including supplementary-plane 𝔹) and is discarded unevaluated — g ≜ λs∈𝔹: s + "!" happily takes a string. Matches the site's "parsed today, not yet enforced" claim. Pins: 039_type_constraint_unenforced.

RULING: BLESS. Type constraints parse and are discarded, unenforced — this is the published claim and Stage 5's mandate.

18. Nullary calls and zero-parameter λ

Question: f() parses — but can any function be called that way? Observed: no zero-parameter λ can be written (λ: e is a parse error), so every f() is a runtime err_arity. The call syntax exists; nothing can satisfy it. Pins: 046_arity_nullary, 047_arity_extra.

RULING: AMEND GRAMMAR. Nullary functions exist: λ: e is admitted (bare colon; the parenthesized spelling stays rejected per ruling 25). Effects made M0 procedural the day entered it; arity 0 is not an exception to . f() calls it; arity mismatches remain err_arity.

19. as multiplication

Question: is (U+2217) an operator, and what does it mean? Observed: exactly × — same token, same precedence. Pins: 041_asterisk_multiplication.

RULING: BLESS. (U+2217) is exactly ×.

20. Divergence: empty program

Question: is the empty program valid? Observed: the grammar accepts it; the JS interpreter rejects it (err_unexpected at 1:1). Pins: none possible until ruled (decision 3 — divergent programs cannot enter the corpus). Repro in DIVERGENCES.md (fuzz index 49).

RULING: OVERRIDE. The empty program is valid and produces no output. The grammar is right; the interpreter accepts it.

21. Divergence: sequences inside parentheses

Question: is ( e1 ; e2 ) valid? DECISIONS.md says (...) contains one seqExpr; the JS parser allows exactly one expression. Observed: the grammar accepts (42;) and (a; b); the JS interpreter rejects both (err_expect). Pins: none until ruled. Repros in DIVERGENCES.md (fuzz indexes 86, 119, 247, 233, 279, 406, 468, 101…).

RULING: OVERRIDE. Parentheses contain one seqExpr per the standing DECISIONS ruling: (e1; e2) is legal, value = last expression. The interpreter implements its own language.

22. Divergence: composition

Question: does function composition exist in M0? Observed: the grammar parses f ∘ g; the JS interpreter lexes (it even has the \circ escape) but has no parse rule for it — every use is err_expect. Pins: none until ruled. Repro in DIVERGENCES.md (fuzz index 9).

RULING: IMPLEMENT. is function composition: (f ∘ g)(args…) = f(g(args…)). Operands must be functions — a non-function operand raises the expected-a-function key (reuse the existing key if the audit finds one, else introduce err_fn). Precedence and associativity follow the grammar.

23. Divergence: set literals

Question: {a, b} is a set per the grammar's brace disambiguation — does the M0 core have sets? Observed: the grammar accepts ({5, "a b"}) and friends; the JS interpreter has no set support and rejects them (err_expect). Pins: none until ruled. Repros in DIVERGENCES.md (multiple fuzz indexes).

RULING: PARSE-ONLY. Set literals parse (the interpreter gains the grammar's brace disambiguation) and evaluation raises err_notyet. Set semantics are an M1 design, logged.

24. Divergence: record literals

Question: {k: v} is a record per the grammar — does the M0 core have records? Observed: the grammar accepts ({y: 0}); the JS interpreter rejects (err_expect). Pins: none until ruled. Repros in DIVERGENCES.md (fuzz indexes 25, 212, 425, 427, 447).

RULING: PARSE-ONLY. Record literals: same as 23.

25. Divergence: parenthesized λ parameters

Question: DECISIONS.md rejected λ(a, b): as a second parameter spelling — but who enforces that? Observed: inverted from every other divergence — the GRAMMAR rejects λ(a, b): e (honoring the decision) while the JS interpreter accepts it. The interpreter carries a syntax form the language explicitly rejected. Pins: none until ruled. Repros in DIVERGENCES.md (fuzz indexes 13, 29, 76, …).

RULING: OVERRIDE. λ(a, b): e is rejected by the interpreter too — the grammar already enforces the ratified DECISIONS ruling.

26. Divergence: bare λ and Greek letters as identifiers

Question: the grammar tokenizes Greek letters (LAMBDA_VAR et al.) as identifiers, so {λ} parses; the JS interpreter only knows λ as the abstraction head. Observed: grammar accepts ({λ}); JS rejects (err_expect — λ demands a parameter list). Pins: none until ruled. Repro in DIVERGENCES.md (fuzz index 52).

RULING: AMEND GRAMMAR. λ is a reserved token, never an identifier. Only λ; other Greek letters (π et al.) remain identifiers — Fatima wants π.