Curry-Howard for term synthesis in Isabelle - isabelle

Say I have proven some basic proposition of intuitionistic propositional logic in Isabelle/HOL:
theorem ‹(A ⟶ B) ⟶ ((B ⟶ C) ⟶ (A ⟶ C))›
proof -
{
assume ‹A ⟶ B›
{
assume ‹B ⟶ C›
{
assume ‹A›
with ‹A ⟶ B› have ‹B› by (rule mp)
with ‹B ⟶ C› have ‹C› by (rule mp)
}
hence ‹A ⟶ C› by (rule impI)
}
hence ‹(B ⟶ C) ⟶ (A ⟶ C)› by (rule impI)
}
thus ?thesis by (rule impI)
qed
I know from the Curry-Howard correspondence that the proposition corresponds to some type (a -> b) -> ((b -> c) -> (a -> c)), and the proof to some term, all inside some type theory (say, in the simply-typed λ-calculus). From the structure of the proof, I know the corresponding simply-typed λ-term is λf:a→b. λg:b→c. λx:a. f(g(x)).
Is there a way to get Isabelle to construct this for me?
I have looked up program extraction in Isabelle, and from what I can tell it largely refers to something else: where you write functional programs in Isabelle, prove things about them and then it provides some kind of translation to Haskell or ML.
I also know that HOL is not the same thing as dependent type theory, which I'm given to understand has a stronger Curry-Howard flavour to it. I know that HOL itself is conceptually somewhat like polymorphic λ-calculus, and I found some brief notes about how HOL is a shallow encoding of logic in type theory, but some more context would be greatly appreciated. I've barely been able to piece together how all these different proof assistants and their associated foundations relate together, and perhaps some more historical context would help too. Unfortunately, the documentation around Isabelle, Coq, etc. all seems a bit all over the place; for Isabelle in particular, I seem to regularly find information that is 20 years out of date.

Related

How do you print local variables and ?thesis in an Isabelle proof (debugging in Isabelle)?

I sometimes find it hard to use Isabelle because I cannot have a "print command" like in normal programming.
For example, I want to see what ?thesis. The concrete semantics book says:
The unknown ?thesis is implicitly matched against any goal stated by lemma or show. Here is a typical example:
My silly sample FOL proof is:
lemma
assumes "(∃ x. ∀ y. x ≤ y)"
shows "(∀x. ∃ y. y ≤ x)"
proof (rule allI)
show ?thesis
but I get the error:
proof (state)
goal (1 subgoal):
1. ⋀x. ∃y. y ≤ x
Failed to refine any pending goal
Local statement fails to refine any pending goal
Failed attempt to solve goal by exported rule:
∀x. ∃y. y ≤ x
but I do know why.
I expected
?thesis === ⋀x. ∃y. y ≤ x
since my proof state is:
proof (state)
goal (1 subgoal):
1. ⋀x. ∃y. y ≤ x
Why can't I print ?thesis?
It's really annoying to have to write the statement I'm trying to proof if it's obvious. Perhaps it's meant to be explicit but in the examples in chapter 5 they get away with using ?thesis in:
lemma fixes a b :: int assumes "b dvd (a+b)" shows "b dvd a" proof −
have "∃k′. a = b∗k′" if asm: "a+b = b∗k" for k proof
show "a = b∗(k − 1)" using asm by(simp add: algebra_simps) qed
then show ?thesis using assms by(auto simp add: dvd_def ) qed
but whenever I try to use ?thesis I always fail.
Why is it?
Note that this does work:
lemma
assumes "(∃ x. ∀ y. x ≤ y)"
shows "(∀x. ∃ y. y ≤ x)"
proof (rule allI)
show "⋀x. ∃y. y ≤ x" proof -
but I thought ?thesis was there to avoid this.
Also, thm ?thesis didn't work either.
Another example is when I use:
let ?ys = take k1 xs
but I can't print ?ys value.
TODO:
why doesn't:
lemma "length(tl xs) = length xs - 1"
thm (cases xs)
show anything? (same if your replaces cases with induction).
You can find ?theorem and others in the print context window:
As for why ?thesis doesn't work, by applying the introduction rule proof (rule allI) you are changing the goal, so it no longer matches ?thesis. The example in the book uses proof- which prevents Isabelle from applying any introduction rule.
It seems I asked a very similar question worth pointing to: What is the best way to search through general definitions, theorems, functions, etc for Isabelle?
But here is a list of thing's I've learned so far:
thm: seems to work for definition, lemmas and functions. For definition do name_def for a definition with name name. For functions do thm f.simps for all definitions in the function. For a single one do thm f.simps(1) for the first one. For lemmas do thm lemma_name or thm impI or HOL.mp etc.
term: for terms do term term_name e.g. in isar term ?thesis or term this
print_theorems: if you place this after a definition or a function it shows all the theorems defined for those! It's amazing.
print... I just noticed in jedit if you let the auto complete show you the rest for print it has a bunch of options! Probably useful!
Search engine for Isabelle: https://search.isabelle.in.tum.de/
You can use Query (TODO: improve this)
TODO: how to find good way to display stuff about tactics.
I plan to update this as I learn all the ways to debug in Isabelle.

Constructing useful lemmas

In the tutorial Programming and Proving in Isabelle/HOL there's a step-by-step explanation of the proof of reversing a list twice yields the original list (2.2.4 The Proof Process).
theorem rev_rev [simp]: "rev(rev xs) = xs"
apply(induction xs)
apply(auto)
Following the auto step one subgoal remains:
1. V x1 xs.
rev (rev xs) = xs =⇒
rev (app (rev xs) (Cons x1 Nil)) = Cons x1 xs
The author then says "In order to simplify this subgoal further, a lemma suggests itself.", and presents the rev_app lemma below:
lemma rev_app [simp]: "rev(app xs ys) = app (rev ys) (rev xs)"
Is it just intuition and practice, just like in pen and paper proofs, that enables one to see how subgoal 1. could be simplified and come up with a lemma like rev_app? I simply can't recognize how this lemma suggests itself.
That is indeed tricky for people unfamiliar with formal proof developments. Over time, one will learn many heuristic approaches to come up with potential lemmas.
In this case (purely equational reasoning) the heuristic usually works by looking at the involved constants of the subgoals.
The main lemma, for example, describes a rev/rev property. The subgoal however needs something about rev/app. This is what tells you that you need a lemma about these two.
The remainder can, unfortunately, be only be described as "human ingenuity": to see that rev(app xs ys) = app (rev ys) (rev xs) is a reasonable property on rev/app.
There is various research on detecting such properties automatically, for example IsaHipster.

How to use obtain to make forward elimination proofs easier to read?

I'm trying to do basic natural deduction proofs in Isabelle, following this document (particularly slide 23).
I know I can do things like
theorem ‹(A ⟶ B) ⟶ A ⟶ B›
proof -
{
assume ‹A ⟶ B›
{
assume ‹A›
with ‹A ⟶ B› have ‹B› ..
}
hence ‹A ⟶ B› ..
}
thus ‹(A ⟶ B) ⟶ A ⟶ B› ..
qed
But also
theorem ‹(A ⟶ B) ⟶ A ⟶ B›
proof
assume ‹A ⟶ B› and ‹A›
then obtain ‹B› ..
qed
achieves the same goal.
So when I try to write the proof
theorem ‹(A ⟶ A ⟶ B) ⟶ A ⟶ B›
proof -
{
assume ‹A ⟶ A ⟶ B›
{
assume ‹A›
with ‹A ⟶ A ⟶ B› have ‹A ⟶ B› ..
hence ‹B› using ‹A› ..
}
hence ‹A ⟶ B› ..
}
thus ‹(A ⟶ A ⟶ B) ⟶ A ⟶ B› ..
qed
like
theorem ‹(A ⟶ A ⟶ B) ⟶ A ⟶ B›
proof
assume ‹A ⟶ A ⟶ B› and ‹A›
hence ‹A ⟶ B› ..
then obtain ‹B› using ‹A› ..
qed
why does Isabelle complain that
Failed to finish proof:
goal (1 subgoal):
1. A ⟶ A ⟶ B ⟹ A ⟶ B
I'm aware that these are very simple things that Isabelle can prove in one step: the goal here is to produce a concise proof which is human readable (to the extent that Natural Deduction is), without having to consult Isabelle.
This modification to your proof works:
theorem ‹(A ⟶ A ⟶ B) ⟶ A ⟶ B›
proof(intro impI)
assume ‹A ⟶ A ⟶ B› and ‹A›
hence ‹A ⟶ B› ..
then show ‹B› using ‹A› ..
qed
The problem is twofold:
The opening of the proof block applied a 'standard' introduction rule automatically based on the shape of the goal you were trying to prove. In your case this was implication introduction, i.e. the theorem impI. The problem is that you only apply this once which leaves you with the assumption A -> A -> B and the remaining goal A -> B. As a result, you do not yet have the assumption A which you are assuming you have as this requires a second use of impI to obtain. Instead, by using proof(intros impI) I am telling Isabelle to refrain from using its standard set of introduction and elimination rules as a first step in the proof and instead apply the impI introduction rule as often as it can (i.e. twice). Alternatively, proof(rule impI, rule impI) would also work here with the same effect.
Second, your final line then obtain onwards, is not finishing the proof: you are not showing anything! By using an explicit show you are signalling to Isabelle that you would like to 'refine' an open goal and actually conclude what it is you set out to prove at the start of the block.
Note that your use of obtain here to work forward from the facts A -> B and A was not incorrect if your only goal is to derive B. The problem is you are trying to work forward from facts to derive new ones at the same time as refine an open goal. For instance, this also works:
theorem ‹(A ⟶ A ⟶ B) ⟶ A ⟶ B›
proof(intro impI)
assume ‹A ⟶ A ⟶ B› and ‹A›
hence ‹A ⟶ B› ..
then obtain ‹B› using ‹A› ..
then show ‹B› .
qed
where the fact B is obtained on the first line, and the second line trivially uses this fact to refine the open goal B.

Instantiating a class from a concrete object?

I'm attempting to formalize a series of proofs about topology from a book [1] in Isabelle.
I want to encode the idea that a topological space (X,T) consists of a set X of "points" (elements of some arbitrary type 'a), and a set of subsets of X, called T, such that:
A1. if an element p is in X, then there exists at least one set N in T that also contains p.
A2. if sets U and V are in T, and if p∈(U∩V), then there must exist at a set N in T where N⊆(U∩V) and x∈N. (If two sets intersect, then there must be a neighborhood that covers the intersection.).
Currently I have the following definition:
class topspace =
fixes X :: "'a set"
fixes T :: "('a set) set"
assumes A1: "p∈X ≡ ∃N∈T. p∈N"
assumes A2: "U∈T ∧ V∈T ∧ x∈(U∩V) ⟹ ∃N∈T. x∈N ∧ N⊆(U∩V)"
begin
(* ... *)
end
So far, so good. I'm able to add various definitions and prove various lemmas and theorems about hypothetical topspace instances.
But how do I actually create one? Unless I'm misinterpreting things, the examples I've seen so far for the instance and instantiate keywords all seem to be been about declaring that one particular abstract class (or type or locale) is an instance of another.
How do I tell Isabelle that a particular pair of sets (e.g. X={1::int, 2, 3}, T={X,{}}) form a topspace?
Likewise, how can I use my definition to prove that X={1::int, 2, 3}, T={} does not fit the requirements?
Finally, once I show that a particular concrete object X meets the definition of a topspace, how do I tell Isabelle to now make use of all the definitions and theorems I've proven about topspace when proving things about X?
BTW, I'm using class because I don't know any better. If it's not the right tool for the job, I'm happy to do something else.
[1]: A Bridge to Advanced Mathematics by Dennis Sentilles
I've made some progress here: a class is a special type of locale, but it isn't necessary for this sort of usage, and using the locale keyword directly simplifies the situation a bit. Every locale has an associated theorem that you can use to instantiate it:
locale topspace =
fixes X :: "'a set"
fixes T :: "('a set) set"
assumes A1 [simp]: "x∈X ≡ ∃N∈T. x∈N"
assumes A2 [simp]: "U∈T ∧ V∈T ∧ x∈(U∩V) ⟹ ∃N∈T. x∈N ∧ N⊆(U∩V)"
theorem
assumes "X⇩A={1,2,3::int}" and "T⇩A={{}, {1,2,3::int}}"
shows "topspace X⇩A T⇩A"
proof
show "⋀U V x. U∈T⇩A ∧ V∈T⇩A ∧ x∈U∩V ⟹ ∃N∈T⇩A. x∈N ∧ N⊆U∩V"
and "⋀x. x∈X⇩A ≡ ∃N∈T⇩A. x∈N" using assms by auto
qed
If we want to use definition for declarations, the proof goal becomes a bit more complex, and we need to use the unfolding keyword. (The locales.pdf that comes with isabelle covers this, but I'm not sure I'm not yet able to explain it in my own words). Anyway, this works:
experiment
begin
definition X⇩B where "X⇩B={1,2,3::int}"
definition T⇩B where "T⇩B={{}, {1,2,3::int}}"
lemma istop0: "topspace X⇩B T⇩B" proof
show "⋀U V x. U∈T⇩B ∧ V∈T⇩B ∧ x∈U∩V ⟹ ∃N∈T⇩B. x∈N ∧ N⊆U∩V"
and "⋀x. x∈X⇩B ≡ ∃N∈T⇩B. x∈N" unfolding X⇩B_def T⇩B_def by auto
qed
end
I believe it's also possible, and possibly preferable, to do all this work inside of a sub-locale, but I haven't quite worked out the syntax for this.
Although locales are implemented in the calculus itself and hence their predicates can be used in any regular proposition, this is usually not recommended. Instead, you should instantiate locales using e.g. interpretation, as in the following example.
locale topspace =
fixes X :: "'a set"
fixes T :: "('a set) set"
assumes A1 [simp]: "x∈X ⟷ (∃N∈T. x∈N)"
assumes A2 [simp]: "U∈T ∧ V∈T ∧ x∈(U∩V) ⟹ ∃N∈T. x∈N ∧ N⊆(U∩V)"
context
fixes X⇩A T⇩A
assumes X⇩A_eq: "X⇩A = {1, 2, 3 :: int}"
and T⇩A_eq: "T⇩A = {{}, {1, 2, 3 :: int}}"
begin
interpretation example: topspace X⇩A T⇩A
by standard (auto simp add: X⇩A_eq T⇩A_eq)
lemmas facts = example.A1 example.A2
end
thm facts
Whether this pattern really fits for your needs depends on your application; if you just want to have a predicate, it is better to define it directly without using locale at all.
Note: there is really need to the Pure equality »≡«; prefer HOL equality »=«, or its syntactic variant »⟷«.

Recognising that a subgoal is proved

I would like to understand the state machine of the Isar Virtual Machine.
Page 48 of Markus Wenzel's doctoral thesis gives a good overview but does not detail its messages in the Output panel. It might well be a later addendum to the system.
I have a simple Isar proof:
theory Propositional
imports Main
begin
lemma nj2: assumes p: P and q: Q shows "P ∧ (Q ∧ P)"
proof -
from q p have qp: "Q ∧ P" by (rule conjI)
from p qp show "P ∧ (Q ∧ P)" by (rule conjI)
qed
after the second by (rule conjI) the Output panel says
show (P::bool) /\ (Q::bool) /\ P
Successful attempt to solve goal by exported rule:
(P::bool) /\ (Q::bool) /\ P
proof (state): depth 0
this:
(P::bool) /\ (Q::bool) /\ P
goal:
No subgoals!
variables:
P, Q :: bool
so it explicitly recognizes the solution of the goal. However, at the first by (rule conjI) it says
have qp: (Q::bool) /\ (P::bool)
proof (state): depth 0
this:
(Q::bool) /\ (P::bool)
goal (1 subgoal):
1. P /\ Q /\ P
variables:
P, Q :: bool
I see no sign that the subgoal has been proved. Or, the fact that the have statement is the same as in the this register should remind me that it is proved?
Well, the subgoals in the output panel correspond to the subgoals of the context. In this case, the context is the one of the complete lemma, beginning with proof -. In this context, there is only one subgoal, which is the lemma to be proved.
When you state your intermediate property with have, the system doesn't verify anything with respect to the goals, and once it's proved, it just gives you access to this property (through the names this and qp) because you have proved it with by (rule conjI) and the fact that there is no error means that it is proved.
On the other hand, when you state a property with show, the system verifies that this property with the eventual assumptions you've made (with assume) actually corresponds to one of the subgoals, and fails otherwise.
When it arrives to the qed command, it finally verifies that all the subgoals of the context have been proved.
Another way to write this proof is like this (I didn't checked it worked, but it should...) :
theory Propositional
imports Main
begin
lemma nj2: assumes p: P and q: Q shows "P ∧ (Q ∧ P)"
proof (rule conjI)
from p show P by assumption
next
from q p show "Q ∧ P" by (rule conjI)
qed
In this case, proof (rule conjI) creates 2 subgoals P and Q ∧ P and the Output panel should confirm this.

Resources