|
|
- ---
- title: "This Sentence is False, or: On Natural Language, Typing and Proof"
- date: September 9th, 2020
- ---
-
- The Liar's paradox is often the first paradox someone dealing with logic, even in an informal setting, encounters. It is _intuitively_ paradoxical: how can a sentence be both true, and false? This contradicts (ahem) the law of non-contradiction, that states that "no proposition is both true and false", or, symbolically, $\neg (A \land \neg A)$. Appealing to symbols like that gives us warm fuzzy feelings, because, _of course, the algebra doesn't lie!_
-
- There's a problem with that the appeal to symbols, though. And it's nothing to do with non-contradiction: It's to do with well-formedness. How do you accurately translate the "this sentence is false" sentence into a logical formula? We can try by giving it a name, say $L$ (for liar), and state that $L$ must represent some logical formula. Note that the equality symbol $=$ here is _not_ a member of the logic we're using to express $L$, it's a symbol of this discourse. It's _meta_logical.
-
- $$ L = \dots $$
-
- But what should fill in the dots? $L$ is the sentence we're symbolising, so "this sentence" must mean $L$. Saying "X is false" can be notated in a couple of equivalent ways, such as $\neg X$ or $X \to \bot$. We'll go with the latter: it's a surprise tool that will help us later. Now we know how to fill in the dots: It's $L \to \bot$.
-
- <details>
- <summary>Truth tables demonstrating the equivalence between $\neg A$ and $A \to \bot$, if you are classically inclined.</summary>
- <div class="mathpar">
- <table>
- <tr>
- <th> $A$ </th>
- <th> $\neg A$ </th>
- </tr>
- <tr><td>$\top$</td><td>$\bot$</td></tr>
- <tr><td>$\bot$</td><td>$\top$</td></tr>
- </table>
- <table>
- <tr>
- <th> $A$ </th>
- <th> $A\to\bot$ </th>
- </tr>
- <tr><td>$\top$</td><td>$\bot$</td></tr>
- <tr><td>$\bot$</td><td>$\top$</td></tr>
- </table>
- </div>
- </details>
-
- But wait. If $L = L \to \bot$, then $L = (L \to \bot) \to \bot$, and also $L = ((L \to \bot) \to \bot) \to \bot$, and so... forever. There is no finite, well-formed formula of first-order logic that represents the sentence "This sentence is false", thus, assigning a truth value to it is meaningless: Saying "This sentence is false" is true is just as valid as saying that it's false, both of those are as valid as saying "$\neg$ is true".
-
- Wait some more, though: we're not done. It's known, by the [Curry-Howard isomorphism], that logical systems correspond to type systems. Therefore, if we can find a type-system that assigns a meaning to our sentence $L$, then there _must_ exist a logical system that can express $L$, and so, we can decide its truth!
-
- Even better, we don't need to analyse the truth of $L$ logically, we can do it type-theoretically: if we can build an inhabitant of $L$, then it is true; If we can build an inhabitant of $\neg L$, then it's false; And otherwise, I'm just not smart enough to do it.
-
- So what is the smallest type system that lets us assign a meaning to $L$?
-
- # A system of equirecursive types: $\lambda_{\text{oh no}}$[^1]
-
- [^1]: The reason for the name will become obvious soon enough.
-
- We do not need a complex type system to express $L$: a simple extension over the basic simply-typed lambda calculus $\lambda_{\to}$ will suffice. No fancy higher-ranked or dependent types here, sorry!
-
- As a refresher, the simply-typed lambda calculus has _only_:
-
- * A set of base types $\mathbb{B}$,
- * Function types $\tau \to \sigma$,
- * For each base type $b \in \mathbb{B}$, a set of base terms $\mathbb{T}_b$,
- * Variables $v$,
- * Lambda abstractions $\lambda v. e$, and
- * Application $e\ e'$.
-
- <details>
- <summary>Type assignment rules for the basic $\lambda_{\to}$ calculus.</summary>
- <div class="math-paragraph">
- <div>
- $$\frac{x : \tau \in \Gamma}{\Gamma \vdash x : \tau}$$
- </div>
- <div>
- $$\frac{b \in \mathbb{B} \quad x \in \mathbb{T}_{b}}{\Gamma \vdash x : b}$$
- </div>
- <div>
- $$\frac{\Gamma, x : \sigma \vdash e : \tau}{\Gamma \vdash \lambda x. e : \sigma \to \tau}$$
- </div>
- <div>
- $$\frac{\Gamma, e : \sigma \to \tau \quad \Gamma \vdash e' : \sigma}{\Gamma \vdash e\ e' : \tau}$$
- </div>
- </div>
- </details>
-
- First of all, we'll need a type to represent the logical proposition $\bot$. This type is empty: It has no type formers. Its elimination rule corresponds to the principle of explosion, and we write it $\mathtt{absurd}$. The inference rule:
-
- <div class="math-paragraph">
- $$\frac{\Gamma \vdash e : \bot}{\mathtt{absurd}\ e : A}$$
- </div>
-
- We're almost there. What we need now is a type former that serves as a solution for equations of the form $v = ... v ...$. That's right: we're just _inventing_ a solution to this class of equations---maths!
-
- These are the _equirecursive_ types, $\mu a. \tau$. The important part here is _equi_: these types are entirely indistinguishable from their unrollings. Formally, we extend the set of type formers with type variables $a$ and $\mu$-types $\mu a. \tau$, where $\mu a$ acts as a binder for $a$.
-
- Since we invented $\mu$ types as a solution for equations of the form $a = \tau$, we have that $\mu a. \tau = \tau[\mu a.\tau/a]$, where $\tau[\sigma{}/a]$ means "substitute $\sigma{}$ everywhere $a$ occurs in $\tau$". The typing rules express this identity, saying that anywhere a term might have one as a type, the other works too:
-
- <div class="math-paragraph">
- <div>
- $$\frac{\Gamma \vdash e : \tau[\mu a.\tau / a]}{\Gamma \vdash e : \mu a. \tau}$$
- </div>
- <div>
- $$\frac{\Gamma \vdash e : \mu a.\tau}{\Gamma \vdash e : \tau[\mu a. \tau / a]}$$
- </div>
- </div>
-
- Adding these rules, along with the one for eliminating $\bot$, to the $\lambda_{\to}$ calculus nets us the system $\lambda_{\text{oh no}}$. With it, one can finally formulate a representation for our $L$-sentence: it's $\mu a. a \to \bot$.
-
- There exists a closed term of this type, namely $\lambda k. k\ k$, which means: The "this sentence is false"-sentence is true. We can check this fact ourselves, or, more likely, use a type checker that supports equirecursive types. For example, OCaml with the `-rectypes` compiler option does.
-
- We'll first define the empty type `void` and the type corresponding to $L$:
-
- <div class="math-paragraph">
- ~~~~{.ocaml}
- type void ;;
- type l = ('a -> void) as 'a ;;
- ~~~~
- </div>
-
- Now we can define our proof of $L$, called `yesl`, and check that it has the expected type:
-
- <div class="math-paragraph">
- ~~~~{.ocaml}
- let yesl: l = fun k -> k k ;;
- ~~~~
- </div>
-
- However. This same function is also a proof that... $\neg L$. Check it out:
-
- <div class="math-paragraph">
- ~~~~{.ocaml}
- let notl (x : l) : void = x x ;;
- ~~~~
- </div>
-
- # I am Bertrand Russell
-
- Bertrand Russell (anecdotally) once proved, starting from $1 = 0$, that he was the Pope. I am also the Pope, as it turns out, since I have on hand a proof that $L$ and $\neg L$, in violation of non-contradiction; By transitivity, I am Bertrand Russell. <span style="float: right; display: inline-block;"> $\blacksquare$ </span>
-
- Alright, maybe I'm not Russell (drat). But I am, however, a trickster. I tricked you! You thought that this post was going to be about a self-referential sentence, but it was actually about typed programming language design (not very shocking, I know). It's a demonstration of how recursive types (in any form) are logically inconsistent, and of how equirecursive types _are wrong_.
-
- The logical inconsistency, we all deal with, on a daily basis. It comes with Turing completeness, and it annoys me to no end every single time I accidentally do `let x = ... x ...`{.haskell}. I _really_ wish I had a practical, total functional programming language to use for my day-to-day programming, and this non-termination _everywhere_ is a great big blotch on Haskell's claim of purity.
-
- The kind of recursive types you get in Haskell is _fine_. They're not _great_ if you like the propositions-as-types interpretation, since it's trivial to derive a contradiction from them, but they're good enough for programming that implementing a positivity checker to ensure your definitions are strictly inductive isn't generally worth the effort.
-
- Unless your language claims to have "zero runtime errors", in which case, if you implement isorecursive types instead of inductive types, you are _wrong_. See: Elm. God damn it.
-
- <details>
- <summary>So much for "no runtime errors"... I guess spinning forever on the client side is acceptable.</summary>
- <div class="flex-list">
-
- ```elm
- -- Elm
- type Void = Void Void
- type Omega = Omega (Omega -> Void)
-
- yesl : Omega
- yesl = Omega (\(Omega x) -> x (Omega x))
-
- notl : Omega -> Void
- notl (Omega x) = x (Omega x)
- ```
-
- </div>
- </details>
-
- Equirecursive types, however, are a totally different beast. They are _basically_ useless. Sure, you might not have to write a couple of constructors, here and there... at the cost of _dramatically_ increasing the set of incorrect programs that your type system accepts. Suddenly, typos will compile fine, and your program will just explode at runtime (more likely: fail to terminate). Isn't this what type systems are meant to prevent?
-
- Thankfully, very few languages implement equirecursive types. OCaml is the only one I know of, and it's gated behind a compiler flag. However, that's a footgun that should _not_ be there.
-
- [Curry-Howard isomorphism]: https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence
|