Documentation

LambdaS.NonDefinability

Non-definability: why pow must be primitive #

pow is a primitive constructor of Λs. The justification has been an argument in prose: from an argument at and literals at 1, closure under + − × ÷ reaches exactly the subgroup generated by , and u is not in it. This file makes that a theorem.

Why the fundamental theorem is not enough #

Applying scaleLaw to a hypothetical term of type Q (u·u) → Q u gives f(k²x) = k·f(x) for every k > 0, hence f(y) = √y · f(1). So parametricity says any such function is square root; it does not say none exists. That is exactly why Kennedy needs a strengthening for his semantic proof: "a more generous notion of scaling environment (homomorphisms from subgroups of Unt)".

The shortcut #

Λs has no if and no rec, so for the arithmetic fragment a syntactic argument suffices and gives a stronger conclusion: the fragment's type is uninhabited, not merely uncomputable.

The key observation is that mul and div add and subtract exponent vectors, so the reachable units form the -span of the arguments' units, not the ℚ-span. Over ℚ we would have u = (u²)^(1/2) and the argument would collapse; over ℤ, u ∉ {u^(2n)}. The distinction between what the unit algebra permits and what the term grammar can build is the whole content, and it is why rational powers have to be given rather than derived.

From the paper's long form: Would recursion rescue square root? #

An earlier draft of the paper (the parent of tag long-form) carried this analysis in full; it is reproduced here, converted to Markdown, so the documentation develops what the paper now states in a sentence.

We might think the result stands only because Λs lacks recursion: given a fixpoint construct, we could write the standard functional implementation of Newton's method and compute square roots. Where would it break? Not in the loop. The iteration x_(n+1) = (x_n + a/x_n)/2 is unit-correct at every step: from x_n : Q u and a : Q u² we obtain a/x_n : Q u, and the mean of two quantities at Q u is again at Q u. The break is at the entry point. The iteration needs a seed x₀ : Q u, and within the arithmetic fragment the theorem governs, no term manufactures one from a : Q u²; that is the non-definability theorem itself, and the artifact records this reading (no_newton_seed; at the term grammar, no_newton_seed_tm).

The full calculus, however, does offer a seed: 1_u inhabits Q u in any context. Does the seed defeat the theorem? Run the seeded program on a = 4 m² and watch what the seed costs. Measured in meters, one step from 1_u gives (1 + 4/1)/2 = 2.5. Measured in centimeters, the same quantity is 40000 cm², and the same step gives 20000.5, which is 200.005 meters. The ideal square root commutes with rescaling, since √(k² x) = k√x; iterates from a seed that itself rescaled would commute too. But 1_u denotes 1 in every unit system (it names a unit, exactly what parametricity excludes, “The Price of Conversion” (Fundamental.lean)), so every finite run of the seeded program depends on the unit system. Nor does a cleverer constant help: any expressible seed q · 1_u denotes the fixed number q everywhere, and what commuting with rescaling needs is not a better constant but a seed that transforms.

The obstruction is exact. Still supposing a fixpoint construct added, terminating runs of a recursive arithmetic program are piecewise rational in their inputs, and square root is not a rational function, so no definable program computes it exactly, whatever its seed strategy. The argument quantifies over the whole program, so it covers seeds tuned by hand, computed from a, or supplied as q · 1_u for any q.

Sharper still: a scale-invariant f at this type satisfies f(k² x) = k f(x) for all k > 0, and setting x = 1 forces f(y) = f(1)√y on the positives, so an exactly invariant definable function, being piecewise rational, is identically zero. A relative stopping tolerance |x_n² - a|/a < ε, which is dimensionless and admissible, bounds the dependence by ε; an absolute tolerance |x_n² - a| < ε is not even writable parametrically, since ε : Q u² names a unit. That bound is the entire dependence. The drift analysis of “Accumulated Ratios, and a Decidable Diagnostic” (Twist.lean) declines terms containing 1_u rather than certifying them, which is here the correct answer.

Alternatively the seed moves into the interface: at Q u² → Q u → Q u the method is definable, parametric, and invariant by the theorem “Abstraction, convert-free” (fundamental_free, Fundamental.lean), because a seed supplied as an argument rescales with everything else. So recursion would not invalidate the theorem; it sharpens its content. A constant-seeded program with a relative tolerance is a perfectly serviceable approximation in practice; its dependence on the unit system is bounded by the ε the programmer chose, the same class of dependence ordinary numerical error already imposes. The theorem's content is not that square roots cannot be computed but that the square root cannot be: what the power primitive buys is exactness, a denotation that commutes with rescaling pointwise rather than up to tolerance.

Rational powers of a single unit #

theorem LambdaS.NonDef.rpow_zero {B : Type} {k : } (w : UExp B k) :
Term.rpow w 0 = 1
theorem LambdaS.NonDef.rpow_one {B : Type} {k : } (w : UExp B k) :
Term.rpow w 1 = w
theorem LambdaS.NonDef.rpow_add {B : Type} {k : } (w : UExp B k) (a b : ) :
(Term.rpow w a).mul (Term.rpow w b) = Term.rpow w (a + b)
theorem LambdaS.NonDef.rpow_sub {B : Type} {k : } (w : UExp B k) (a b : ) :
(Term.rpow w a).div (Term.rpow w b) = Term.rpow w (a - b)

The reachable units #

InSpan S u says u can be built from the units in S using exactly the operations the term grammar provides: the trivial unit (from a literal), the generators (from variables), and multiplication and division.

inductive LambdaS.NonDef.InSpan {B : Type} {k : } (S : List (UExp B k)) :
UExp B kProp

The units reachable from S by the operations of the arithmetic fragment.

Instances For
    theorem LambdaS.NonDef.inSpan_single {B : Type} {k : } {w v : UExp B k} (h : InSpan [w] v) :
    ∃ (n : ), v = Term.rpow w n

    From a single generator, the reachable units are exactly its integer powers. This is where mul/div being addition and subtraction of exponents (rather than arbitrary ℚ-scaling) does its work.

    theorem LambdaS.NonDef.not_inSpan_sq {B : Type} {k : } {u : UExp B k} (hu : u 1) :

    The square root of a unit is unreachable.

    If u is nontrivial, then u is not in the span of : it would have to be u^(2n) for an integer n, and 1 - 2n is a nonzero integer, so every exponent of u (base or variable) would be zero. The hypothesis is exactly u ≠ 1: a bare unit variable, the ∀u. Q u² → Q u reading, is covered.

    The arithmetic fragment, intrinsically typed by its unit #

    inductive LambdaS.NonDef.Arith {B : Type} {k : } (S : List (UExp B k)) :
    UExp B kType

    Expressions of the arithmetic fragment over a context of scalar units, indexed by the unit of their result: literals, variables, and the field operations.

    Instances For
      theorem LambdaS.NonDef.arith_inSpan {B : Type} {k : } {S : List (UExp B k)} {u : UExp B k} (e : Arith S u) :
      InSpan S u

      Every unit an arithmetic expression can have is reachable from its context.

      theorem LambdaS.NonDef.sqrt_not_definable {B : Type} {k : } {u : UExp B k} (hu : u 1) :

      Square root is not definable.

      Given only a quantity of unit , no arithmetic expression has unit u. The type is uninhabited: not merely hard to reach, and not merely uncomputable.

      So pow is not a convenience: without it the field operations cannot express the square root of a dimensioned quantity at all: no expression they build even has the right unit. (For full Λs the syntactic claim needs the caveat that ucon inhabits Q u in any context, so the type is inhabited; what this result pins down is that arithmetic alone never reaches the unit.) Together with the admissibility criterion (a primitive may take any type whose denotation is scale-invariant, and is) this says pow is both necessary and permitted.

      theorem LambdaS.NonDef.no_newton_seed {B : Type} {k : } {u : UExp B k} (hu : u 1) :

      The same statement for Newton's iteration, which is where the intuition usually goes. The iteration itself is well-typed ((x + a/x)/2 has unit u whenever x does), but no seed of unit u exists to start it, so the whole construction is unavailable. Kennedy's Exercise 14 hint in miniature.

      Lifting to the term grammar of Λs itself #

      sqrt_not_definable speaks about Arith, a grammar defined in this file. The design claim is about Λs: no term of the calculus built from variables, rational literals, multiplication, division, and addition computes a square root. This section closes the gap between the two. Tm.ArithOnly carves the arithmetic fragment out of Tm, in the style of Tm.Parametric and Tm.ConvertFree; arith_of_hasTy reflects every typing derivation of an arithmetic term over a scalar context into Arith; and the grammar-level theorem then transfers verbatim.

      The caveat recorded at sqrt_not_definable, that ucon inhabits Q u in any context so the bare type is inhabited, disappears here: ucon is not an arithmetic operation and ArithOnly excludes it, so the term-level statement needs no qualification.

      def LambdaS.Tm.ArithOnly {B D : Type} {j k : } :
      Tm B D j kProp

      A term of Λs lies in the arithmetic fragment when it is built from variables, rational literals, multiplication, division, and addition, and nothing else. In particular ucon, pow, convert, log, exp, and every binder and application form are excluded. Same style as Tm.Parametric and Tm.ConvertFree: a predicate on terms, not on derivations, which by uniqueness of derivations loses nothing.

      Equations
      Instances For
        theorem LambdaS.NonDef.arith_of_hasTy {B D : Type} [Fintype B] [UnitSys B D] {j k : } {Δ : DCtx D j k} {us : List (UExp B k)} {e : Tm B D j k} {w : UExp B k} :
        ∀ (a : HasTy Δ (scalarCtx us) e (Ty.Q w)), e.ArithOnlyNonempty (Arith us w)

        Reflection. A typing derivation of an arithmetic term over a scalar context lands in the Arith grammar. scalarCtx us assigns type Q us[n] to de Bruijn variable n, and Arith.var records only membership of the unit in the context, so the variable case collapses to a lookup. Everything else is the observation that each arithmetic typing rule is an Arith constructor.

        theorem LambdaS.NonDef.sqrt_not_definable_tm {B D : Type} [Fintype B] [UnitSys B D] {j k : } {Δ : DCtx D j k} {u : UExp B k} (hu : u 1) :
        ¬∃ (e : Tm B D j k) (x : HasTy Δ (scalarCtx [Term.mul u u]) e (Ty.Q u)), e.ArithOnly

        Square root is not definable in Λs. The paper's sentence, at the calculus's own term grammar: given a single argument of unit , no Λs term built from variables, rational literals, and the field operations has type Q u. This is sqrt_not_definable lifted from the self-contained Arith grammar to Tm via the reflection, and the ucon caveat is gone because ArithOnly excludes ucon.

        theorem LambdaS.NonDef.no_newton_seed_tm {B D : Type} [Fintype B] [UnitSys B D] {j k : } {Δ : DCtx D j k} {u : UExp B k} (hu : u 1) {e : Tm B D j k} (d : HasTy Δ (scalarCtx [Term.mul u u]) e (Ty.Q u)) :

        The Newton-iteration reading at the term level: any well-typed Λs candidate for a seed of unit u from an argument at must step outside the field operations. The iteration body (x + a/x)/2 is well-typed at Q u; the seed is what cannot be written.

        Conversion is not definable either #

        The results above concern sqrt, and there they are weaker than the literature: Atkey, Johann and Kennedy (POPL'13, Examples 6 and 8) prove non-definability of square root semantically, in Coq, and go further: cube root stays undefinable even if square root is added as a primitive.

        This section proves something their framework cannot state, because their calculus has no operation that reads a unit: convert is a genuine primitive. Any parametric, convert-free function from Q u to Q v is constantly zero whenever some scaling separates u from v. Parametricity matters, since λx. (x / ucon u) · ucon v is convert-free and denotes the identity at that type. So conversion is not a derived operation dressed up as a constructor, and the coherence hypothesis on fundamental is not the price of a redundant feature.

        The proof is the semantic one (apply fundamental_free at a scaling that fixes u and moves v), which is exactly AJK's technique, used on a question that only arises once conversion exists.

        theorem LambdaS.NonDef.scale_ofBase {B : Type} [Fintype B] [DecidableEq B] {k : } (ψ : Scaling B k) (b : B) :

        The scale factor of a bare generator is the exponential of its entry.

        noncomputable def LambdaS.NonDef.separate {B : Type} [DecidableEq B] {k : } (_b c : B) :

        A scaling that fixes one generator and doubles another. Two base units of the same dimension are separated by such a scaling, which is exactly the freedom Scaling.Coherent forbids, and the freedom that makes conversion observable.

        Equations
        Instances For
          theorem LambdaS.NonDef.separate_fixes {B : Type} [Fintype B] [DecidableEq B] {k : } {b c : B} (h : b c) :
          theorem LambdaS.NonDef.separate_moves {B : Type} [Fintype B] [DecidableEq B] {k : } (b c : B) :
          theorem LambdaS.NonDef.convertFree_eq_zero {B D : Type} [Fintype B] [UnitSys B D] {j k : } {Δ : DCtx D j k} {u v : UExp B k} {e : Tm B D j k} (d : HasTy Δ [] e ((Ty.Q u).arrow (Ty.Q v))) (hp : e.Parametric) (hf : e.ConvertFree) (V : Scaling B k) {ψ : Scaling B k} (h1 : ψ.scale u = 1) (h2 : ψ.scale v 1) (x : ) :
          den V d PUnit.unit x = 0

          Conversion cannot be simulated. A convert-free function from Q u to Q v is the zero function, as soon as one scaling fixes u and moves v.

          This is the statement fundamental_free was built to make: no coherence hypothesis, so ψ may be chosen adversarially, and the adversarial choice collapses the function.

          theorem LambdaS.NonDef.convert_not_definable {B D : Type} [Fintype B] [DecidableEq B] [UnitSys B D] {j k : } {Δ : DCtx D j k} {b c : B} (hbc : b c) (V : Scaling B k) {e : Tm B D j k} (d : HasTy Δ [] e ((Ty.Q (Term.ofBase b)).arrow (Ty.Q (Term.ofBase c)))) (hp : e.Parametric) (hf : e.ConvertFree) :

          So convert is primitive. No convert-free term denotes conversion between two distinct base units, because conversion is multiplication by a positive factor and the only convert-free candidate is zero.

          Together with fundamental this is the design claim, proved in both directions: conversion cannot be removed from the language, and keeping it costs exactly coherence.