--- tags: chapman, programming languages --- # Theorem proving (2019) The aim of the lecture is to show that the idea of automatically generating a parser from a context free grammar can also be applied to automatically generating a proof checker from the rules of logic. To generate a parser from a context free grammar, we used BNFC. To generate a proof checker from a logical theory, we use two different theorem proves known as # Isabelle and Idris ## Learning Outcomes This lecture can only skim the surface of a rich and deep field. Here are some highlights worth remembering. - *Interactive Theorem Provers* or *Proof Assistants* can be used to construct interactively and check automatically mathematical proofs. - Two example proofs that show the similarity of programming and proving. - Dependent types. - Propositions-as-types and Curry-Howard isomorphism. - In the proposition-as-types paradigm - proofs are programs and programs are proofs - mathematical induction is recursion - the inductive case in a proof is performed by recursively calling the proof (=program) on a smaller argument. ## Introduction [Continuing the lecture on induction](https://hackmd.io/@m5rnD-8SSPuuSHTKgXvMjg/BkHZL3jFS), I want to give the briefest introduction to two programming languages, Isabelle and Idris. Inductive definitions and proofs can be implemented in both Isabelle and Idris in a nice way and we will look at [NumExp in Isabelle](https://github.com/alexhkurz/programming-languages-2019/blob/master/NumExp.thy) and at [NumExp in Idris](https://github.com/alexhkurz/programming-languages-2019/blob/master/NumExp.idr). [Isabelle](https://isabelle.in.tum.de) is a special purpose programming language which can do a lot of things (even general programming), but is designed to program mathematical proofs. To explain the fundamentals of how it works we look in some detail at how to prove $1+n\approx n+1$. **Exercise:** Install [Isabelle](https://isabelle.in.tum.de) and open the file [NumExp](https://github.com/alexhkurz/programming-languages-2019/blob/master/NumExp.thy). Read through the file in the Isabelle IDE to get a sense of how a formalisation of the previous lecture looks in an interactive theorem prover. **Remark:** For those who want to learn more, a good point to start working with Isabelle is the book [Concrete Semantics](http://concrete-semantics.org), which is available for free online. [Idris](https://www.idris-lang.org/) is a general purpose programming language inspired by Haskell, but with a much more powerful type system: Idris has so-called *dependent types*. And this allows us to use Idris not only as a programming language but also as a theorem prover. Indeed, types can be used to formulate properties of programs as types. For example, the $\approx$ relation becomes a type and the proof of $1+n\approx n+1$ a program of that type. **Exercise:** (I assume here that you have done the exercise on Isabelle above first.) Install [Idris](http://docs.idris-lang.org/en/latest/tutorial/starting.html). Download [NumExp](https://github.com/alexhkurz/programming-languages-2019/blob/master/NumExp.idr). Run `idris NumExp.idr` from the command line. Run `eval_num (S O)` (where `O` is the letter `O`, not the number `0`). Consult the code in `NumExp.idr` to understand the result. Run your own variations. Compare with the Isabelle code in `NumExp.idr`. Run `plusone O`, `plusone (S O)`, etc. Try to understand in what sense the output of running `plusone (S O)` is a proof that `O+(S O)=(S O)+O`. Finally you can also run larger examples such as `comm_plus (S O) (S (S O))`. It quickly becomes clear, though, that the output is not meant to be read by humans. **Remark:** In our Idris definition of `Num`, we pronounce the symbol `O` as "One". But feel free to read it as `0`. For what follows, it really makes no difference whether we start the numbers at zero or one. ## The language of arithmetic expressions The language of `num` and `exp` we defined above num ::= 1 | S num exp ::= num | exp + exp | exp * exp is in Isabelle defined as datatype num = One | S num datatype exp = Num num | Plus exp exp | Mult exp exp We replace `1, +, *` with `One, Plus, Mult`, because the former already have a meaning in Isabelle. But this is only notation. Worth noticing is that we replace `exp::=num` by `exp=Num num`. Why the extra occurrence of `Num`? One way to think about this is as a type casting which turns a `num` into an `exp`. We didn't need to write it in our mathematical notation above because we used the convention that numbers are written as $n, \ldots$ and expressions as $e,\ldots$. Isabelle allows us to get closer to the mathematical notation by introducing some syntactic sugar, which makes the actual code look like (but the simpler one above would work just as well) datatype num = One ("𝟭") | S num ("_+𝟭") datatype exp = Num num ("⟨_⟩") | Plus exp exp (infix ":+:" 14) | Mult exp exp (infix ":*:" 15) In Idris the language is defined as data Num = O | S Num data Exp = N Num | (:+:) Exp Exp | (:*:) Exp Exp where we read `O` as "One". ## Implementing a Logical Theory In our example, the logical theory consists of - the rules of equational reasoning - the axiom of associativity for expressions - the axiom $Sk=k+1$ relating the plus-one on numbers to the plus-one on expressions This logical theory can be discribed in both Isablle and Idris in a similar way. In Isabelle: inductive equal_exp :: "exp ⇒ exp ⇒ bool" where equal_exp_refl: "e ≡ex e" | equal_exp_symm: "e1 ≡ex e2 ⟹ e2 ≡ex e1" | equal_exp_trans: "e1 ≡ex e2 ⟹ e2 ≡ex e3 ⟹ e1 ≡ex e3" | equal_exp_cong_plus: "e1 ≡ex e1' ⟹ e2 ≡ex e2' ⟹ e1 :+: e2 ≡ex e1' :+: e2'" | equal_exp_plusone: "⟨n+𝟭⟩ ≡ex ⟨n⟩ :+: ⟨𝟭⟩" | equal_exp_assoc: "(e1 :+: (e2 :+: e3)) ≡ex ((e1 :+: e2) :+: e3)" In Idris: data (:=:) : Exp -> Exp -> Type where EqExpRefl : e :=: e EqExpSymm : e1 :=: e2 -> e2 :=: e1 EqExpTrans : e1 :=: e2 -> e2 :=: e3 -> e1 :=: e3 EqExpPlusEq : e1 :=: e1' -> e2 :=: e2' -> e1 :+: e2 :=: e1' :+: e2' EqExpPlusOne : (N (S n)) :=: (N n) :+: (N O) EqExpAssoc : e1 :+: (e2 :+: e3) :=: (e1 :+: e2) :+: e3 These two snippets of code look almost the same. Almost all differences are purely notational and not interesting. The one big difference that is interesting is that Isabelle has `bool` in a place where Idris has `Type`. In Isabelle we formalise $\approx$ by a function `equal_exp` that takes two expressions and returns a truth value. This allows us to say in Isabelle whether an equation $e\approx e'$ is derivable or not. In Idris we decided to write `:=:` instead of $\approx$ (not important difference) and `e :=: e'` is not a boolean but a type (important). So `e :=: e'` is treated in the same way as `Num`or `Exp`. Indeed, `e :=: e'` is a data type. Notice that the type `e :=: e'` is *dependent* on the terms `e` and `e'`. So why does it make it sense to say that the proposition `e :=: e'` is a type? ## Propositions-As-Types and Curry-Howard In fact, and this is known as the "Propositions as Types" paradigm, or also as the Curry-Howard-Isomorphism, it is possible to think of propositions and of types as interchangable concepts: Intuitively, a type can be identified with the set of elements that have this type and a proposition can be identified with the set of elements of which the proposition is true. Similarly, a program that computes a value of type $T$ corresponds to a proof that the proposition $T$ is true. **Example:** In `NumExp.idr`, there is a function plusone which has type (n : Num) -> (N O) :+: (N n) :=: (N n) :+: (N O) that is, `plusone` takes as input a number `n` and outputs a term of type `(N O) :+: (N n) :=: (N n) :+: (N O)`, that is, `plusone n` outputs a proof that $1+n\approx n+1$. **Remark:** The difference of the proposition $1+n\approx n+1$ and the type `(N O) :+: (N n) :=: (N n) :+: (N O)` is purely notational, the main difference being that in the Idris code we need to "typecast" the number `n` into an expression `N n`, whereas this distinction is kept implicit in the mathematical notation. **Exercise:** Run `plusone O` and explain in which sense the output EqExpRefl : (N O) :+: (N O) :=: (N O) :+: (N O) is indeed a proof of $1+1\approx 1+1$. **Exercise:** Run `plusone (S O)` and explain in which sense the output is indeed a proof of $1+2\approx 2+1$. ## Proofs We now go back to the previous lecture. Recall **Proposition 1:** $\quad 1+Sk \approx Sk +1$ *Proof.* $$ 1+Sk \approx 1+ (k+1) \approx (1+k)+1 \approx (k+1)+1 \approx Sk +1$$ **Exercise 1:** Recall the rules ${\ \rm (refl)\ } , {\ \rm (sym)\ } , {\ \rm (trans)\ } , {\ \rm (cong)\ } , {\ \rm (assoc)\ }$ from the previous lecture. You will also need $Sk\approx k+1$ and the induction hypothesis. Annotate each "$\approx$" above with the name of the rule justifying it. **Solution 1** is in the footnote and best read only after attempting your own solution, but needed for what follows. [^solution1] In both Isabelle and Idris, one has essentially to program the proof. That is, there will be a sequence of commands, or a nested call of functions, telling the program which rule to apply in which order. ### Automated Proof Checking in Isabelle Let us do Isabelle first. I copy in the Isabelle code, but this should really be looked at in the Isabelle IDE. The reason why the code is unreadable as it is, is that it only shows the rules that are applied, but not the so-called "proof state", that is, what the rules are supposed to prove. In the Isabelle IDE, the proof state corresponding to each line of the code can be made visible in the "Output" pane. Anyway, the idea is that with a bit of care, one can see how the program below corresponds to the mathematical proof we reviewed in Solution 1. Even now, just looking at the raw code, you should be able to identify the rules that have been applied. **Exercise:** Can you reconstruct the proof from the code below? (* Proposition 1 in the lecture notes. *) lemma plusone: "⟨𝟭⟩ :+: ⟨n⟩ ≡ex ⟨n⟩ :+: ⟨𝟭⟩" apply (induction n) apply (rule equal_exp_refl) apply(rule equal_exp_trans) apply(rule equal_exp_cong_plus) apply(rule equal_exp_refl) apply(rule equal_exp_plusone) apply(rule equal_exp_trans) apply(rule equal_exp_assoc) apply(rule equal_exp_trans) apply(rule equal_exp_cong_plus) apply simp apply(rule equal_exp_refl) apply(rule equal_exp_cong_plus) apply(rule equal_exp_symm) apply(rule equal_exp_plusone) apply(rule equal_exp_refl) done Isabelle is being developed with the aim to make machine proofs readable to humans as much as possible. And, indeed, one can write the Isabelle source code above also in a much more readable form (using the *tactic* `exp_tac`, the source code of which is also available in `NumExp.thy`): lemma plusone: "⟨𝟭⟩ :+: ⟨n⟩ ≡ex ⟨n⟩ :+: ⟨𝟭⟩" proof (induction n) case One then show ?case by (simp add: equal_exp_refl) next case (S m) have "⟨𝟭⟩ :+: ⟨m+𝟭⟩ ≡ex ⟨𝟭⟩ :+: (⟨m⟩ :+: ⟨𝟭⟩)" by exp_tac also have "… ≡ex (⟨𝟭⟩ :+: ⟨m⟩) :+: ⟨𝟭⟩" by exp_tac also have "… ≡ex (⟨m⟩ :+: ⟨𝟭⟩) :+: ⟨𝟭⟩" by(exp_tac rule:S) also have "… ≡ex (⟨m+𝟭⟩) :+: ⟨𝟭⟩" by exp_tac finally show ?case by simp qed If we translate the above into "pseudo-code", it almost looks like the mathematics proof: lemma plusone: "1+n ≡ex n+1" proof (induction n) case One then show ?case by (simp add: equal_exp_refl) next case (S m) have "1 + ⟨m+1⟩ ≡ex 1 + (m + 1)" also have "… ≡ex ( 1 + m ) + 1" also have "… ≡ex ( m + 1 ) + 1" also have "… ≡ex ⟨m+1⟩ + 1" finally show ?case by simp qed Indeed, replacing `⟨m+1⟩` by $Sm$, this is now easy to match with the mathematical proof $$ 1+Sm = 1+ (m+1) = (1+m)+1 = (m+1)+1 = Sm +1$$ I find this close match between math and programming very pleasing and I would say, well done, Isabelle. What about Idris? ### Automated Proof Checking in Idris total plusone : (n : Num) -> (N O) :+: (N n) :=: (N n) :+: (N O) plusone O = EqExpRefl plusone (S n) = EqExpTrans (EqExpPlusEq EqExpRefl EqExpPlusOne) (EqExpTrans EqExpAssoc (EqExpTrans (EqExpPlusEq (plusone n) EqExpRefl) (EqExpPlusEq (EqExpSymm EqExpPlusOne) EqExpRefl))) Wow ... what is going on here? The lecture is coming to end $\ldots$ so I have to leave this an exercise. But I give you some hints: - A rule like `EqExpTrans` is now a function that takes as arguments two proofs and produces a new proof. - The proof itself is then a function as well, named `plusone`. - The induction step is now implemented as the function `plusone` calling itself recursively. I am very excited about this last item $\ldots$ we started surveying different forms of induction and ended up discovering that inductive proofs and recursive programs are the same. **Remark:** The general paradigm is called "proposition as types" more often than "proofs as programs". To understand what is meant by this let us look at the Idris code snippet total plusone : (n : Num) -> (N O) :+: (N n) :=: (N n) :+: (N O) Ignoring `total`, we can read - `plusone : _` as "`plusone` is an element of type `_`" - `(n : Num) -> _` as "for all `n` of type `Num` there is an element of type `_`" - `(N O) :+: (N n) :=: (N n) :+: (N O)` as "$1+n=n+1$" In other words, the proposition "$1+n=n+1$" can be read as a type. But the word "type" is short for "data type" and a data type is a set of data elements. So what are the elements here? [^elementsoftypes] **Remark:** I recommend to install Idris and run the code. But I pasted in below the result of calling `plusone` on the argument 3 (remember that `O` stands here for "One"). Can you explain what happens? *NumExp> plusone (S (S O)) EqExpTrans (EqExpPlusEq EqExpRefl EqExpPlusOne) (EqExpTrans EqExpAssoc (EqExpTrans (EqExpPlusEq (EqExpTrans (EqExpPlusEq EqExpRefl EqExpPlusOne) (EqExpTrans EqExpAssoc (EqExpTrans (EqExpPlusEq EqExpRefl EqExpRefl) (EqExpPlusEq (EqExpSymm EqExpPlusOne) EqExpRefl)))) EqExpRefl) (EqExpPlusEq (EqExpSymm EqExpPlusOne) EqExpRefl))) : (N O) :+: (N (S (S O))) :=: (N (S (S O))) :+: (N O) *NumExp> [^solution1]: Solution to Exercise 1. \begin{align} 1+Sk &\approx 1+ (k+1) \quad & Sk \approx k+1\\ 1+ (k+1) & \approx (1+k)+1 & \textrm{(assoc)}\\ (1+k)+1 &\approx (k+1)+1 & \textrm{IH, (cong)}\\ (k+1)+1 &\approx Sk +1 & Sk \approx k+1\\ 1+Sk &\approx Sk +1 & 3\times\textrm{(trans)}\\ \end{align} [^elementsoftypes]: Answer: The elements are the proofs `plusone n` for all `n` in `Num`.