14 KiB
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 (007 → 7,
0.50 → 0.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" < 9 → false, true < 2 → true; 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 ∧ true → false 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 π.