Practal — Practical Logic
A Bicycle for Your Mathematical Mind
July 17th, 2021
Abstract
This is a first sketch of the design of Practal, an interactive theorem proving system for practical logic.
Introduction
I agree with the importance of types for ITP, especially for the formalization of mathematics.
Ganesalingam [2] discusses in detail the role of type in mathematics. Nevertheless it seems to me that popular ITP systems do not capture the notion of type in quite the right way to accommodate common mathematical use. Isabelle/HOL and HOL Light for example are based on simply-typed higher-order logic (HOL), which is often sufficient for purposes of program verification, and
astonishingly versatile [3]. But the simple types of HOL do not allow for dependent typing; the type of real square matrices with
n rows and columns cannot be expressed in HOL as a type in a straightforward way (although
Harrison did it anyway [4]). On the other hand,
dependent types [5] as implemented in Coq or Lean bestow a lot of power onto types via the
proposition as types [6] paradigm (see also the
Xena blog for a discussion), but do not even allow for the common mathematical practice of
subtyping (neither does HOL). Instead,
coercions are used to emulate some of the advantages of subtyping.
Impressive results have been obtained with current ITP technology, a recent one being the
Liquid Tensor Experiment, a formalization in Lean based on ideas of Fields medallist Peter Scholze.
Still, in my opinion, ITP technology has not yet progressed as far as one could reasonably hope for. Among other things, there are two things I would like to do with an ITP system:
- Pick a book or paper, and formalize its ideas within the ITP system to build and further my understanding.
- Formalize my own notions and ideas (e.g. Local Lexing [7]) for the same reasons.
Both of these things are already possible with current ITP systems. But there is a caveat: It takes too much time and effort to do so. I want the ITP system to be a true bicycle for my mind that sharpens and accelerates my thinking, not a heavy boulder that I need to roll up a hill, consuming all of my energy. This also implies that the results of the formalization, including the proofs it contains, are in close correspondence with how I actually think about the notion at hand. I want a system for practical logic. I want Practal.
So how to get there? In the end, an ITP system is just software. So all the usual advice for making software applies. Probably the most important such advice is to gather requirements and feedback from actual and prospective users. As a start, I will fill this user role myself.
But you are invited to become a user of Practal as well!
My design methodology will be to just do with Practal what I would like to do with an ITP system. The difficulty is of course that Practal does not exist yet, but I will just pretend that it does. Concretely, I will write down formalizations that I wish Practal would understand. Of course I will also have in mind what I believe Practal will be able to do, but a major driver will be what I want it to do.
In particular, I will not start from a particular logic and type system, and then build everything around it. Instead, I will let use cases shape both. This might lead to intermediate inconsistencies, which can hopefully be fixed along the way. Note that this approach is the opposite to how ITP systems are usually designed: First comes the logic / type system as a given, and then users are invited to find ingenious solutions to their formalization needs!
In the rest of this paper I will examine first use cases, and outline initial design decisions for Practal.
Use Case: Category Theory
In my opinion, the ability to apply both dependent typing and subtyping together is essential to a natural formalization of mathematics. Let’s consider the following hypothetical definition of categories in Practal:
structure Category =
type Object
type Arrow
let dom : Arrow → Object
let cod : Arrow → Object
def HomSet(A : Object, B : Object) =
{ f : Arrow | dom f = A ∧ cod f = B }
let id : (x : Object) → HomSet(x, x)
let then : (f : Arrow) → (g : Arrow | dom g = cod f) →
{ h : Arrow | dom h = dom f ∧ cod h = cod g }
axiom identity : ∀ x : Arrow. (x then id (cod x)) = (id (dom x) then x) = x
axiom associativity : ∀ e f g : Arrow. ((e then f) then g) = (e then (f then g))
end
Just for this simple example we are already heavily using dependent types in the form of dependent ordered pairs to form the structure itself, and dependent function types for elements of the structure like
then. These are combined with
predicate subtypes [8] as
{ f : Arrow | dom f = A ∧ cod f = B }
for the definition of HomSet(A, B). It is a term of type Arrow → ℙ and can itself be viewed as a type, in particular a subtype of Arrow.
The notation (g : Arrow | dom g = cod f) in the declaration of then is short for
(g : { g : Arrow | dom g = cod f })
Note that the axiom for associativity does not include the precondition
cod e = dom f ∧ cod f = dom g
This is because if this condition is not met, then both (e then f) then g = nil holds as well as e then (f then g) = nil, which makes (e then f) then g = e then (f then g) true anyway.
Note that equality is a special operator. It takes two arguments, and returns whether they are equal or not. Its type would be = : Any → Any → ℙ, but because there is no Any type in Practal, equality does not have a type.
Nil Type For Expressing Undefinedness
In the above use case, why does (e then f) then g = nil hold when the precondition is not met?
It is because for any function f : A → B and an argument x such that ¬(x : A), i.e. not x : A, we define f x to be equal to nil, where nil is the only value of type Nil. We also define that nil applied to any argument x yields nil, that is nil x = nil.
So assume cod e ≠ dom f. This implies e then f = nil, because ¬(f : { g : Arrow | dom g = dom e}). Thus ((e then f) then g) = (nil then g). Now, we would certainly think that ¬(nil : Arrow), and therefore furthermore (nil then g) = nil. We can arrange for this indeed being the case by letting the declaration type Arrow being an abbreviation for
let Arrow : Type
axiom ¬(nil : Arrow)
Similarly, assume now cod f ≠ dom g. If e then f is defined, then cod (e then f) = cod f and therefore (e then f) then g = nil.
In our approach,
nil is just a normal value; there is nothing special about it except for a few conventions that make it easy for
nil to represent undefinedness or optionality. We will say more about this in a
later section →.
Function Subtyping
Given that Practal supports predicate subtyping, it is natural to ask which other subtyping relationships exist. In particular, given two function types A → B and C → D, when do we consider A → B to be a subtype of C → D, in symbols A → B ≤ C → D?
C ≤ A ∧ B ≤ D
This is problematic. Consider two functions floor : ℝ → ℤ and ceiling : ℝ → ℤ which round down and round up, respectively, a real number to an integer. Clearly floor ≠ ceiling. But if we allowed above subtyping convention, then floor and ceiling would both also have type ℤ → ℤ. But floor : ℤ → ℤ is just the identity, and ceiling : ℤ → ℤ is just the identity as well, so we would now have to conclude floor = ceiling!
The problem here is that in a logic we are often interested in whether two functions f and g are equal, i.e. whether f = g holds. In a programming language, this is usually not something we are interested in, as f = g is generally not computable anyway, and therefore of no interest. But in our logic, two functions f : A → B and g : A → B are equal iff
∀ x : A. f x = g x
holds. Therefore, in order to talk consistently about function equality, we need to be able to uniquely determine the domain A of any function f. We therefore treat A → B ≤ C → D as being equivalent to
A = C ∧ B ≤ D
Note that this is also in line with our notion of undefinedness for function application. If a function f : A → B could also have type f : C → B with A ≠ C, then there would be no consistent answer to the question if f x is defined or not, because we could have x : A but ¬(x : C) (or the other way around).
Union, Intersection and Difference Types
Given that we allow subtypes, it makes sense to also allow both union types A ∪ B and intersection types A ∩ B. Naturally, we have
A ≤ A ∪ B ∧ B ≤ A ∪ B
for unions, and
A ∩ B ≤ A ∧ A ∩ B ≤ B
for intersections. An intersection can be empty, of course, and there is a type Null to accommodate this fact, i.e.
¬ (∃x : Null. true)
Unions and intersections can be characterized as follows:
∀ A B : Type. ∀ a : A. (a : B) = (a : A ∩ B)
∀ A B : Type. ∀ b : B. (b : A) = (b : A ∩ B)
∀ A B : Type. ∀ a : A. a : A ∪ B
∀ A B : Type. ∀ b : B. b : A ∪ B
Similarly, it is also possible to form the difference A \ B of two types.
Type Extensionality
An obvious question is, when are two types equal? The logical answer seems to be to treat types just like you would treat sets: They are equal if they have the same members:
∀ A B : Type. (A = B) = ((∀ a : A. a : B) ∧ (∀ b : B. b : A))
Partial Functions
Partial functions can be modelled as total functions which can also return nil:
typealias A ↪ B = A → (B ∪ Nil)
Note that we have
A → B ≤ A ↪ B
because function subtyping is covariant in the codomain.
Traditional Approach to Undefinedness
Farmer’s approach avoids three-valued truth values as in
strong Kleene logic [11]. This is certainly a good thing as our goal is to emulate mathematical practice. Therefore in Practal, values in
ℙ are also just
true or
false. But when considered in a truth context, we treat
nil the same way as
false. That makes sense, because just as we cannot prove a theorem
false we neither can prove a theorem
nil. This justification might sound silly; after all we also cannot prove a theorem
3, yet we do not consider
3 to mean
false. But it is the pragmatic answer to the fact that
nil can show up in a truth context, while
3 will not. For example, consider the formula
1/0 < 2
Is this a theorem? No, of course it is not. But then, because of (1/0 < 2) = nil, nil is not true, i.e. nil is false.
Therefore we utilise Practal’s support for subtyping and simply define our logical connectives on
ℙ ∪ Nil instead of just on
ℙ:
Logical Connective
∧
∨
⟶
⟷
¬
Type
ℙ ∪ Nil → ℙ ∪ Nil → ℙ
ℙ ∪ Nil → ℙ ∪ Nil → ℙ
ℙ ∪ Nil → ℙ ∪ Nil → ℙ
ℙ ∪ Nil → ℙ ∪ Nil → ℙ
ℙ ∪ Nil → ℙ
Our strategy for defining the connectives is simple; because we consider
nil and
false from a truth perspective to be the same, we convert all
nil arguments to
false before computing the result. Proceeding in this way, we obtain the following tables for
∧,
∨,
⟶ and
¬:
∧
false
true
nil
false
false
false
false
true
false
true
false
nil
false
false
false
⟶
false
true
nil
false
true
false
true
true
true
true
true
nil
true
false
true
∨
false
true
nil
false
false
true
false
true
true
true
true
nil
false
true
false
¬
false
true
nil
true
false
true
Equivalence
⟷ is defined as follows, and contrasted with equality:
⟷
false
true
nil
false
true
false
true
true
false
true
false
nil
true
false
true
=
false
true
nil
false
true
false
false
true
false
true
false
nil
false
false
true
Similarly, quantifiers ∀ and ∃ work on partial predicates. They both have type
(α : Type) → (α ↪ ℙ) → ℙ
Assume P : α ↪ ℙ. We define ∀ and ∃ as follows:
- If for all x : α we have P x = true then (∀ x : α. P x) = true, otherwise (∀ x : α. P x) = false.
- If there is x : α such that P x = true then (∃ x : α. P x) = true, otherwise (∃ x : α. P x) = false.
Furthermore, the Hilbert choice operator ε has type
(α : Type) → (α ↪ ℙ) ↪ α
and we define it such that if there is x : α with P x = true, then P (ε x : α. P x) = true, otherwise (ε x : α. P x) = nil.
∀ x y z : ℝ. z = x / y ⟶ z ⋅ y = x
should not be a theorem because y = 0 provides a counter example. I don’t agree. The assumption is that for real numbers x,y and z we have z = x / y. This also implies that y cannot be zero, because otherwise x / y would not be a real number.
In Farmer’s approach as well as in Practal above formula is a theorem, albeit for a different reason. While Farmer’s approach and Practal’s approach are closely related, they are not the same. Consider again the formula 1/0 < 2. In Farmer’s approach (1/0 < 2) = false, but in Practal (1/0 < 2) = nil holds. A bigger deviation can be observed for the formula 1/0 = 2/0. This formula is false in Farmer’s approach because the binary predicate = is applied to undefined terms. In Practal this formula is a theorem, because 1/0 = nil and 2/0 = nil both hold.
One of the advantages of
nil being specially treated in some situations, but otherwise being a normal value, is that users of Practal can customize undefinedness and optionality for their own particular use case. For example, for defining partial recursive functions you might want to define and use the logical operators from
strong Kleene logic [11] as well.
Defining Types
There are various ways to define new types:
- via structure
- via typealias
- via typedef
structure
For example, in the category use case a new type called Category is introduced. Each instance of the type can be thought of as a record with the corresponding fields, fulfilling the given axioms, and tagged with the name Category to distinguish it from all other records.
typealias
Another way to introduce a new type is via typealias, but the newly introduced type is not really new, but just an abbreviation for the original type. For example, we have
∀ A B : Type. (A ↪ B ) = (A → (B ∪ Nil))
typedef
A third way of introducing a new type is via typedef. We could have also introduced the type of partial functions as follows:
typedef A ↪ B = A → (B ∪ Nil)
The difference to the typealias version is that this type of partial functions consists of entirely new elements. A way to think of the elements of the new type conceptually is as pairs
(tag,f) where
tag is
Partial-Function, and
f has the type
A → (B ∪ Nil). In particular, the following inequality would then be true:
∀ A B : Type. (A ↪ B) ≠ (A → (B ∪ Nil))
Quotient Types
A most common tool employed by mathematicians is the forming of quotients, i.e. forming a new type from an existing type by identifying elements based on an equivalence relation. Also, often when creating quotients one will want to inject existing types into it. We can accommodate both by augmenting a typedef with identify clauses, for example when creating the rational numbers ℚ from the integers ℤ:
typedef ℚ = ℤ ⨯ (ℤ \ {0})
identify (a, b) in ℚ with (c, d) in ℚ where a ⋅ d = c ⋅ b
identify z in ℤ with (z, 1) in ℚ
This results in the rational numbers ℚ such that ℤ ≤ ℚ holds, i.e. the integers form a subtype of the rational numbers. We can leave out the in ℚ phrases for the sake of more concise notation and write:
typedef ℚ = ℤ ⨯ (ℤ \ {0})
identify (a, b) with (c, d) where a ⋅ d = c ⋅ b
identify z in ℤ with (z, 1)
Similarly, ℤ could have been constructed from ℕ via
typedef ℤ = ℕ ⨯ ℕ
identify (a, b) with (c, d) where a + d = c + b
identify n in ℕ with (n, 0)
and thus both ℕ ≤ ℤ and ℕ ≤ ℚ would hold.
One can see now that typealias is really just a special case of typedef. Instead of
typealias B = A
one could also write
typedef B = A
identify a in A with a in B
Use Case: Set Theory
When asked what the foundation of mathematics is, most mathematicians will answer "set theory". But it is clear that most mathematicians will not reduce the math they do to set theory, and that concepts from orthodox set theory like ordinal numbers do not play a role in their practice.
In my opinion, types as proposed for Practal are closer to mathematical practice than normal set theory. Practal types are really sets in (not much of a) disguise. The type of sets with elements from A can be modelled often satisfactorily by
typealias Set (A : Type) = A → ℙ
On the other hand, if a mathematician wants to use full Zermelo-Fraenkel set theory, then they should be able to do so in Practal. A simple way to achieve this is to declare a structure that captures the axioms of ZF set theory:
structure ZF =
type Set
let ∈ : Set → Set → ℙ
let ∅ : Set
let Un : Set → Set
let Pow : Set → Set
let Repl : Set → (Set → Set) → Set
let Sep : Set → (Set → ℙ) → Set
let Inf : Set
axiom Empty: ∀ x : Set. ¬ (x ∈ ∅)
axiom Extensionality: ∀ x y : Set. (x = y) = (∀ z : Set. (z ∈ x) = (z ∈ y))
axiom Union: ∀ z x : Set. (z ∈ Un x) = (∃ y : Set. z ∈ y ∧ y ∈ x)
axiom Power: ∀ x y : Set. (y ∈ Pow x) = (∀ z : Set. z ∈ y ⟶ z ∈ x)
axiom Replacement: ∀ f : Set → Set. ∀ y X : Set. y ∈ (Repl X f) = (∃ x : Set. x ∈ X ∧ y = f x)
axiom Separation: ∀ f : Set → ℙ. ∀ x X : Set. x ∈ (Sep X f) = (x ∈ X ∧ f x)
axiom Regularity: ∀ X : Set. X ≠ ∅ ⟶ (∃ x : Set. x ∈ X ∧ (∀ y : Set. y ∈ x ⟶ ¬ (y ∈ X)))
def UPair(x : Set, y : Set) = Repl (Pow (Pow ∅)) (λ z. if z = ∅ then x else y)
def Singleton(x : Set) = UPair(x, x)
def union(x : Set, y : Set) = Un (UPair(x, y))
def Suc(x : Set) = union x (Singleton x)
axiom Infinity: ∅ ∈ Inf ∧ (∀ x : Set. x ∈ Inf ⟶ Suc x ∈ Inf)
end
We then presume the existence of an instance of this structure:
let 𝕌 : ZF
A set s : 𝕌.Set can be viewed as a type via
{ x : 𝕌.Set | x ∈ s }
and is therefore tightly integrated with other formalizations in Practal. For example, one should be able to prove in Practal that the subsets of a ZF set together with their set-theoretic functions form a category as defined via the Category structure.
I believe that this way of approaching normal set theory is superior to solutions internal to set theory, like for example the one based on
urelements I outlined in a
mathoverflow post. But both approaches have a lot in common, so it might make sense to combine them somehow.
Harrison makes an
emphatic case for the use of sets in ITP instead of types. I think Practal types provide the flexibility that Harrison calls for. In particular, it is not necessary to decide between formalizing a concept as a predicate or as a type, because in Practal, a predicate can also be used as a type without friction.
Controlled Natural Language
Paraconsistency
I just recently learnt about
paraconsistent logics [16], so my excitement about it is fresh and untainted. The idea behind paraconsistent logics is to prevent the phenomenon of
explosion that occurs in classical logic when both
H and
¬H can be proven for some proposition
H. Because in classical logic
H and
¬H together imply
false, any proposition
G can now be proven although it might have nothing to do with
H.
I don’t know whether supporting paraconsistency would make using Practal awkward. It is intriguing that paraconsistency can be considered a dual notion to intuitionism, which is very popular in ITP circles.
What I find interesting about supporting paraconsistency is that it might make playing around with possibly inconsistent objects possible without infecting otherwise supposedly consistent theories. But maybe providing a "Practal playground" where users can assume arbitrary axioms without endangering the consistency of other formalizations is an easier solution for this.
Finally, what if the features of Practal that I have outlined cannot be implemented in a consistent way? There are many gotchas in logic, like
Russell’s paradox or
Girard’s paradox [17]. I don’t believe that this is the case for Practal,
but if indeed a practical logic as envisioned cannot be consistent, maybe paraconsistency is the solution.
Automation
Considering the most impressive strides that artificial intelligence has taken in the last decade or so, Practal is being designed under the assumption that powerful automation can be achieved. This assumption has already been partially validated by projects like
Isabelle/Sledgehammer that are based on classical automated theorem proving. Current AI projects like
Github Copilot only provide more evidence to support this assumption. In particular, type checking does not have to be decidable; it just has to be certifiable. The assumption of strong automation being achievable frees us from many design constraints that would hinder us in making Practal as useful and pleasant to use as possible.
Conclusion
About seventeen years ago I was told that asking questions is easy, but providing answers is difficult. That is true. What is also true is that if you don’t ask the right question, then the answer does not really matter.
When it comes to Practal, I believe the right question is the following: How do we build an ITP system that is a true bicycle for the mathematical mind? Achieving this goal would mean mathematically inclined people using Practal willingly and happily; because it helps them to think better and faster, instead of slowing them down and hindering their creativity.
References
[14]Mirna Džamonja, Angeliki Koutsoukou-Argyraki, Lawrence C. Paulson. (2020). Formalising Ordinal Partition Relations Using Isabelle/HOL, https://doi.org/10.17863/CAM.70018.