This is the third part of a series on type theory. This time we’re developing real, classical logic in type theory. However, before we can get there, we have to talk about equality first; specifically, propositional equality, which is different from judgmental equality.
Up until now, we’ve always used this weird triple equality “” when defining stuff. This is judgmental equality, and, like type judgments, it is a “meta” operation; it can’t be used within propositions. The following unfortunately doesn’t make any sense: “”, because “” doesn’t return a truth value that our logic can do anything with. This is of course a problem.
Another problem is that, by default, judgmental equality is very strict – especially for functions![1] This is why we might want to expand the concept of equality and assume axioms that declare two things to be equal that weren’t equal before, but, again, we can’t do that with “”, because we can’t use it in logic statements, so we can’t formulate axioms about it.
The solution is to define a new equality, which will be a relation defined wholly within our logic.
As you’ll recall, predicates are functions from values to truth values (i.e., true and false). Relations are the same, except that they expect more than one argument (usually two). Examples of relations are: less than (), greater than (), subset of (), element of ().
But the most important relation of all is probably equality. We could write equality in function notation: , but everyone writes it in infix notation, , and so will we. But keep in mind that it’s actually a type-valued function with two arguments, even if we’re not writing it like that.
If we’re precise, equality is actually a generic function with respect to the type of its arguments. So, it needs to accept three arguments where the first argument is a type:
Just from this type we can tell that only elements of the same type can be equal to each other. In infix notation, we indicate the type argument with a subscript: . However, we will often drop it if it’s clear from context. (Just like we can drop type annotations in functional programing languages if the compiler can infer them from context!)
Now, which things should be equal in our new equality? Type theorists have condensed the essence of equality and have come up with the following definition: for all elements of any type , is equal to itself. That is, the following type is inhabited:
In other words: equality is reflexive. We can then take this as the starting point to prove that other things are equal to other things.
(You might wonder whether this definition will really make our new equality behave like equality should behave. To really convince you of that, we would probably need to consider other possible definitions and compare the results. But I won’t do that here, so you’ll have to take it on faith that this is a good definition.)
The big question is now, when to use this equality and when to use the one from before: “”? My short advice: whenever possible, use judgmental equality, , otherwise use propositional equality, . That’s because judgmental equality is a stronger statement. It means the symbols on the two sides of are completely interchangeable. In fact, “” directly implies “”. Proof: is true by definition; now replace one of the ’s with (we can do this because we have , which implies complete substitutability) now we have .
However, this might not always be true in the other direction. So, whenever we define something, we use judgmental equality because why use a weaker equality when we can use a stronger one?
But we aren’t done yet with the definition of propositional equality. We already said that one essential characteristic of equality is that all values are equal to themselves. Another characteristic is a sort of “induction principle”: if two things are equal, then everything that’s true about one of them should also be true of the other.
More formally: if is true (where is some predicate/property with ) and is true, then is also true. This is guaranteed by the following type’s inhabitedness:
(The existence of this function is still part of the definition; it’s not a theorem. It’s just like how the binary product type came with projection functions.) The type implies that if , then can be substituted for in any proposition that we can express in our type system. If stands for the proposition that is an even number, then if , is also an even number. Within our logic, we cannot distinguish between two elements that are propositionally equal (though they may still be distinguishable with judgmental equality).
We can use this principle to prove that equality is transitive: define where is arbitrary, and plug this into the induction principle from above:
Meaning: if and , then we also have . More informally we could have argued: if and , then replace with in the first equality and we get . In a similar manner, we can easily prove that propositional equality is symmetric: start with , which is always true, then given , replace the first with .
As another example, say that and are any two types, and let’s say that is the relation that holds true iff there is a function from to .[2] Then obviously holds for any because of the identify function . Now, if , replace the second with . We get: . Thus, equality between two types asserts that there must be a function between them (in both directions actually).
So, this is our definition of (propositional) equality. It mostly behaves like you would expect it to: if two things are equal, they can’t be distinguished. However, this equality is a bit too strict for functions, which is why we’ll introduce the principle of function extensionality later on, but for now let’s just accept it as it is.
As a reminder, our goal is now to assume DNE/LEM as an axiom in order to get classical logic:
However, for reasons that I myself don’t fully understand, we’ll get big problems if we just assume DNE/LEM for all types. But luckily, there is no problem if we assume DNE/LEM only for types that have either zero or exactly one element. This includes and but also, for example, the type which contains one function: , and the type which contains a single pair: . And in terms of types with zero elements, there is but also, for example, .
However, if we only assume DNE/LEM for these types, then all of our logic must only produce types with either zero or exactly one element, otherwise we will get propositions for which DNE/LEM doesn’t hold! This means we’ll have to check whether we can make our predicates and logic operators work with this restricted collection of types. That’s what we’ll do in a second, but let’s take another look at the big picture first.
With hindsight, we can see that when we went from “a proposition-as-type is true if it is the type ” to “a proposition-as-type is true if it is inhabited at all” in the previous article, we went too far. In order to get to classical logic, we should have said “a proposition-as-type is true if it is basically like , i.e., it contains exactly one element”. Indeed, we will see that this new idea will work out splendidly.
Types with either zero or exactly one element are sometimes called -types, but this is a terrible name, so we’ll call them subsingletons instead, which is actually a term from set theory, but it describes the concept well. (The name comes from the fact that sets with one element are called singletons, but we’re also allowing sets with fewer elements than that, so it’s a sub-singleton.) If we ignore for the moment the question of whether the operator will work correctly with propositions-as-subsingleton-types, we can define a predicate that tells us whether a given type is a subsingleton, based on the definition of equality that we conveniently just learned about:
Meaning that is inhabited if all elements of are equal to each other. If is the empty type, this is vacuously true, i.e., will just be our good friend, the empty function. If contains just one element, , then is of course inhabited as well; namely by an element of the type “” which, by the definition of equality, is inhabited for any . We will take the above definition as the official definition of a subsingleton: that all its elements are equal to each other.[3]
I called the above predicate “” because we will be using subsingletons to represent truth values, as mentioned before. Classical logic is a two-valued logic, which means the truth values are true (also denoted by ⊤), and false (denoted ⊥). And so we use subsingletons to encode these truth values: if the type has no elements, it corresponds to , and if it has a single element, it corresponds to . Note in particular that any type with exactly one element can represent and any type with zero elements can represent .
But now we have to address the important question of whether we can still use , , , and “” to encode , , , and , when working with propositions as subsingleton types (so that we can safely apply DNE/LEM to all propositions). We’ll need to make sure that the operations we use will result in subsingleton types if they’re getting subsingleton types as inputs.
Say that and are subsingleton types. Is one as well then? Well, it kind of makes sense that there is only one way to map one element to one element. So we’ll assume for the moment that this checks out if both and have a single element (but we’ll revisit this question later when we talk about function extensionality). Conversely, if is , then can only contain the empty function which is also unique. And if is while is not, then has no elements at all, which is also fine for a subsingleton type.
How about ? Here, we can also be certain that there can be at most one element; namely if both types have an element: , , and zero elements otherwise.
Now, let’s say we have a predicate, , such that is a subsingleton type for any . Then is
a subsingleton as well? An element of this type can only exist if there is anything to map the ’s to. So, it requires that all the are inhabited types (and by assumption, they can at most have a single element). Furthermore, it makes intuitive sense that there is only one way to map a given to a single value, which allows us to conclude that if all the ’s are singletons, then is a singleton as well. As I keep mentioning, we will revisit equality of functions later and make it rigorous, but for now let’s just use the intuitive notion where two functions are equal if all their outputs are equal, so:
And this is easy to prove here for two functions . We’ll just invoke the fact that for all , we have (by the assumption that is a singleton). Thus, for all .
We now turn our attention to and it’s pretty clear that there is a problem here. If both and have an element (say, and respectively), then will have two elements: and . Similarly, if for there is more than one for which has an element (say, and and ), then will have more than one element; in this case and .
But, as I said before, this is good news! We don’t need and anyway, because we already have , and .
The last one to investigate is negation, , which we encoded as . This is entirely unproblematic because the type is a subsingleton for any type , not just subsingletons. This is because the only element this type can ever contain is the empty function. If we again use the intuitive notion of function equality, then it’s easy to show that for , we have
if we already know that is a subsingleton. Do we know that is a subsingleton? Well, on the one hand, there are no elements, so we can make the vacuously-true argument mentioned before. On the other hand, if we assume we have two elements, , then we’re again in the position to prove anything, including , via the principle of explosion. So either way, we may conclude that is a subsingleton.
By the way, the fact that that the type is a subsingleton for any type is somewhat intriguing… let’s put a pin in that; it might become relevant later!
And that’s all we need in order to encode classical logic in type theory:
| First-order Logic | Type Theory |
|---|---|