header {* Nominal Isabelle Tutorial at POPL'11 ==================================== Nominal Isabelle is a definitional extension of Isabelle/HOL, which means it does not add any new axioms to higher-order logic. It just adds new definitions and an infrastructure for 'nominal resoning'. The jEdit Interface ------------------- The Isabelle theorem prover comes with an interface for jEdit. Unlike many other theorem prover interfaces (e.g. ProofGeneral) where you advance a 'checked' region in a proof script, this interface immediately checks the whole buffer. The text you type is also immediately checked. Malformed text or unfinished proofs are highlighted in red with a little red 'stop' signal on the left-hand side. If you drag the 'red-box' cursor over a line, the Output window gives further feedback. Note: If a section is not parsed correctly, the work-around is to cut it out and paste it back into the text (cut-out: Ctrl + X; paste in: Ctrl + V; Cmd is Ctrl on the Mac) Nominal Isabelle and jEdit can be started by typing on the command line isabelle jedit -l HOL-Nominal2 isabelle jedit -l HOL-Nominal2 A.thy B.thy ... Symbols ------- Symbols can considerably improve the readability of your statements and proofs. They can be input by just typing 'name-of-symbol' where 'name-of-symbol' is the usual latex name of that symbol. A little window will then appear in which you can select the symbol. With `Escape' you can ignore any suggestion. There are some handy short-cuts for frequently used symbols. For example short-cut symbol => \<Rightarrow> ==> \<Longrightarrow> --> \<longrightarrow> ! \<forall> ? \<exists> /\ \<and> \/ \<or> ~ \<not> ~= \<noteq> : \<in> ~: \<notin> For nominal the following two symbols have a special meaning \<sharp> sharp (freshness) \<bullet> bullet (permutation application)*}theory Tutorial1imports Lambdabeginsection {* Theories *}text {* All formal developments in Isabelle are part of a theory. A theory needs to have a name and must import some pre-existing theory. The imported theory will normally be Nominal2 (which provides many useful concepts like set-theory, lists, nominal things etc). For the purpose of this tutorial we import the theory Lambda.thy, which contains already some useful definitions for (alpha-equated) lambda terms.*}section {* Types *}text {* Isabelle is based on simple types including some polymorphism. It also includes overloading, which means that sometimes explicit type annotations need to be given. - Base types include: nat, bool, string, lam (defined in the Lambda theory) - Type formers include: 'a list, ('a \<times> 'b), 'c set - Type variables are written like in ML with an apostrophe: 'a, 'b, \<dots> Types known to Isabelle can be queried using the command "typ".*}typ nattyp booltyp string typ lam -- {* alpha-equated lambda terms defined in Lambda.thy *}typ name -- {* type of (object) variables defined in Lambda.thy *}typ "('a \<times> 'b)" -- {* pair type *}typ "'c set" -- {* set type *}typ "'a list" -- {* list type *}typ "lam \<Rightarrow> nat" -- {* type of functions from lambda terms to natural numbers *}text {* Some malformed types - note the "stop" signal on the left margin *}(*typ boolean -- {* undeclared type *} typ set -- {* type argument missing *}*)section {* Terms *}text {* Every term in Isabelle needs to be well-typed. However they can have polymorphic type. Whether a term is accepted can be queried using the command "term". *}term c -- {* a variable of polymorphic type *}term "1::nat" -- {* the constant 1 in natural numbers *}term 1 -- {* the constant 1 with polymorphic type *}term "{1, 2, 3::nat}" -- {* the set containing natural numbers 1, 2 and 3 *}term "[1, 2, 3]" -- {* the list containing the polymorphic numbers 1, 2 and 3 *}term "(True, ''c'')" -- {* a pair consisting of the boolean true and the string "c" *}term "Suc 0" -- {* successor of 0, in other words 1::nat *}term "Lam [x].Var x" -- {* a lambda-term *}term "App t1 t2" -- {* another lambda-term *}term "x::name" -- {* an (object) variable of type name *}term "atom (x::name)" -- {* atom is an overloaded function *}text {* Lam [x].Var is the syntax we made up for lambda abstractions. If you prefer, you can have your own syntax (but \<lambda> is already taken up for Isabelle's functions). We also came up with the type "name" for variables. You can introduce your own types of object variables using the command atom_decl: *}atom_decl identatom_decl ty_vartext {* Isabelle provides some useful colour feedback about its constants (black), free variables (blue) and bound variables (green).*}term "True" -- {* a constant defined somewhere...written in black *}term "true" -- {* not recognised as a constant, therefore it is interpreted as a free variable, written in blue *}term "\<forall>x. P x" -- {* x is bound (green), P is free (blue) *}text {* Formulae in Isabelle/HOL are terms of type bool *}term "True"term "True \<and> False"term "True \<or> B"term "{1,2,3} = {3,2,1}"term "\<forall>x. P x"term "A \<longrightarrow> B"term "atom a \<sharp> t" -- {* freshness in Nominal *}text {* When working with Isabelle, one deals with an object logic (that is HOL) and Isabelle's rule framework (called Pure). Occasionally one has to pay attention to this fact. But for the moment we ignore this completely.*}term "A \<longrightarrow> B" -- {* versus *}term "A \<Longrightarrow> B"term "\<forall>x. P x" -- {* versus *}term "\<And>x. P x"section {* Inductive Definitions: Evaluation Relation *}text {* In this section we want to define inductively the evaluation relation and for cbv-reduction relation. Inductive definitions in Isabelle start with the keyword "inductive" and a predicate name. One can optionally provide a type for the predicate. Clauses of the inductive predicate are introduced by "where" and more than two clauses need to be separated by "|". One can also give a name to each clause and indicate that it should be added to the hints database ("[intro]"). A typical clause has some premises and a conclusion. This is written in Isabelle as: "premise \<Longrightarrow> conclusion" "premise1 \<Longrightarrow> premise2 \<Longrightarrow> \<dots> premisen \<Longrightarrow> conclusion" There is an alternative way of writing the latter clause as "\<lbrakk>premise1; premise2; \<dots> premisen\<rbrakk> \<Longrightarrow> conclusion" If no premise is present, then one just writes "conclusion" Below we give two definitions for the transitive closure*}inductive eval :: "lam \<Rightarrow> lam \<Rightarrow> bool" (infixr "\<Down>" 60)where e_Lam[intro]: "Lam [x].t \<Down> Lam [x].t"| e_App[intro]: "\<lbrakk>t1 \<Down> Lam [x].t; t2 \<Down> v'; t[x::=v'] \<Down> v\<rbrakk> \<Longrightarrow> App t1 t2 \<Down> v"text {* Values and cbv are also defined using inductive. *}inductive val :: "lam \<Rightarrow> bool" where v_Lam[intro]: "val (Lam [x].t)"section {* Theorems *}text {* A central concept in Isabelle is that of theorems. Isabelle's theorem database can be queried using *}thm e_Appthm e_Lamthm conjIthm conjunct1text {* Notice that theorems usually contain schematic variables (e.g. ?P, ?Q, \<dots>). These schematic variables can be substituted with any term (of the right type of course). When defining the predicates beta_star, Isabelle provides us automatically with the following theorems that state how they can be introduced and what constitutes an induction over them. *}thm eval.introsthm eval.inductsection {* Lemmas / Theorems / Corollaries *}text {* Whether to use lemma, theorem or corollary makes no semantic difference in Isabelle. A lemma starts with "lemma" and consists of a statement ("shows \<dots>") and optionally a lemma name, some type-information for variables ("fixes \<dots>") and some assumptions ("assumes \<dots>"). Lemmas also need to have a proof, but ignore this 'detail' for the moment. Examples are*}lemma alpha_equ: shows "Lam [x].Var x = Lam [y].Var y" by (simp add: lam.eq_iff Abs1_eq_iff lam.fresh fresh_at_base)lemma Lam_freshness: assumes a: "atom y \<sharp> Lam [x].t" shows "(y = x) \<or> (y \<noteq> x \<and> atom y \<sharp> t)" using a by (auto simp add: lam.fresh Abs_fresh_iff) lemma neutral_element: fixes x::"nat" shows "x + 0 = x" by simptext {* Note that in the last statement, the explicit type annotation is important in order for Isabelle to be able to figure out what 0 stands for (e.g. a natural number, a vector, etc) and which lemmas to apply.*}section {* Isar Proofs *}text {* Isar is a language for writing formal proofs that can be understood by humans and by Isabelle. An Isar proof can be thought of as a sequence of 'stepping stones' that start with some assumptions and lead to the goal to be established. Every such stepping stone is introduced by "have" followed by the statement of the stepping stone. An exception is the goal to be proved, which need to be introduced with "show". have "statement" \<dots> show "goal_to_be_proved" \<dots> Since proofs usually do not proceed in a linear fashion, labels can be given to every stepping stone and they can be used later to explicitly refer to this corresponding stepping stone ("using"). have label: "statement1" \<dots> \<dots> have "later_statement" using label \<dots> Each stepping stone (or have-statement) needs to have a justification. The simplest justification is "sorry" which admits any stepping stone, even false ones (this is good during the development of proofs). have "outrageous_false_statement" sorry Assumptions can be 'justified' using "by fact". have "assumption" by fact Derived facts can be justified using have "statement" by simp -- simplification have "statement" by auto -- proof search and simplification have "statement" by blast -- only proof search If facts or lemmas are needed in order to justify a have-statement, then one can feed these facts into the proof by using "using label \<dots>" or "using theorem-name \<dots>". More than one label at a time is allowed. For example have "statement" using label1 label2 theorem_name by auto Induction proofs in Isar are set up by indicating over which predicate(s) the induction proceeds ("using a b") followed by the command "proof (induct)". In this way, Isabelle uses defaults for which induction should be performed. These defaults can be overridden by giving more information, like the variable over which a structural induction should proceed, or a specific induction principle, such as well-founded inductions. After the induction is set up, the proof proceeds by cases. In Isar these cases can be given in any order. Most commonly they are started with "case" and the name of the case, and optionally some legible names for the variables used inside the case. In each "case", we need to establish a statement introduced by "show". Once this has been done, the next case can be started using "next". When all cases are completed, the proof can be finished using "qed". This means a typical induction proof has the following pattern proof (induct) case \<dots> \<dots> show \<dots> next case \<dots> \<dots> show \<dots> \<dots> qed Two very simple example proofs are as follows.*}subsection {* EXERCISE 1 *}lemma eval_val: assumes a: "val t" shows "t \<Down> t"using aproof (induct) case (v_Lam x t) show "Lam [x]. t \<Down> Lam [x]. t" sorryqedsubsection {* EXERCISE 2 *}text {* Fill the gaps in the proof below. *}lemma eval_to_val: assumes a: "t \<Down> t'" shows "val t'"using aproof (induct) case (e_Lam x t) show "val (Lam [x].t)" sorrynext case (e_App t1 x t t2 v v') -- {* all assumptions in this case *} have "t1 \<Down> Lam [x].t" by fact have ih1: "val (Lam [x]. t)" by fact have "t2 \<Down> v" by fact have ih2: "val v" by fact have "t [x ::= v] \<Down> v'" by fact have ih3: "val v'" by fact show "val v'" sorryqedsection {* Datatypes: Evaluation Contexts*}text {* Datatypes can be defined in Isabelle as follows: we have to provide the name of the datatype and a list its type-constructors. Each type-constructor needs to have the information about the types of its arguments, and optionally can also contain some information about pretty syntax. For example, we like to write "\<box>" for holes.*}datatype ctx = Hole ("\<box>") | CAppL "ctx" "lam" | CAppR "lam" "ctx"text {* Now Isabelle knows about: *}typ ctxterm "\<box>"term "CAppL"term "CAppL \<box> (Var x)"subsection {* MINI EXERCISE *}text {* Try and see what happens if you apply a Hole to a Hole? *}section {* Machines for Evaluation *}type_synonym ctxs = "ctx list"inductive machine :: "lam \<Rightarrow> ctxs \<Rightarrow>lam \<Rightarrow> ctxs \<Rightarrow> bool" ("<_,_> \<mapsto> <_,_>" [60, 60, 60, 60] 60)where m1: "<App t1 t2, Es> \<mapsto> <t1, (CAppL \<box> t2) # Es>"| m2: "val v \<Longrightarrow> <v, (CAppL \<box> t2) # Es> \<mapsto> <t2, (CAppR v \<box>) # Es>"| m3: "val v \<Longrightarrow> <v, (CAppR (Lam [x].t) \<box>) # Es> \<mapsto> <t[x ::= v], Es>"text {* Since the machine defined above only performs a single reduction, we need to define the transitive closure of this machine. *}inductive machines :: "lam \<Rightarrow> ctxs \<Rightarrow> lam \<Rightarrow> ctxs \<Rightarrow> bool" ("<_,_> \<mapsto>* <_,_>" [60, 60, 60, 60] 60)where ms1: "<t,Es> \<mapsto>* <t,Es>"| ms2: "\<lbrakk><t1,Es1> \<mapsto> <t2,Es2>; <t2,Es2> \<mapsto>* <t3,Es3>\<rbrakk> \<Longrightarrow> <t1,Es1> \<mapsto>* <t3,Es3>"declare machine.intros[intro] machines.intros[intro]section {* EXERCISE 3 *}text {* We need to show that the machines-relation is transitive. Fill in the gaps below. *}lemma ms3[intro]: assumes a: "<e1, Es1> \<mapsto>* <e2, Es2>" and b: "<e2, Es2> \<mapsto>* <e3, Es3>" shows "<e1, Es1> \<mapsto>* <e3, Es3>"using a bproof(induct) case (ms1 e1 Es1) have c: "<e1, Es1> \<mapsto>* <e3, Es3>" by fact show "<e1, Es1> \<mapsto>* <e3, Es3>" sorrynext case (ms2 e1 Es1 e2 Es2 e2' Es2') have ih: "<e2', Es2'> \<mapsto>* <e3, Es3> \<Longrightarrow> <e2, Es2> \<mapsto>* <e3, Es3>" by fact have d1: "<e2', Es2'> \<mapsto>* <e3, Es3>" by fact have d2: "<e1, Es1> \<mapsto> <e2, Es2>" by fact show "<e1, Es1> \<mapsto>* <e3, Es3>" sorryqedtext {* Just like gotos in the Basic programming language, labels often reduce the readability of proofs. Therefore one can use in Isar the notation "then have" in order to feed a have-statement to the proof of the next have-statement. This is used in the second case below.*}lemma assumes a: "<e1, Es1> \<mapsto>* <e2, Es2>" and b: "<e2, Es2> \<mapsto>* <e3, Es3>" shows "<e1, Es1> \<mapsto>* <e3, Es3>"using a bproof(induct) case (ms1 e1 Es1) show "<e1, Es1> \<mapsto>* <e3, Es3>" by factnext case (ms2 e1 Es1 e2 Es2 e2' Es2') have ih: "<e2', Es2'> \<mapsto>* <e3, Es3> \<Longrightarrow> <e2, Es2> \<mapsto>* <e3, Es3>" by fact have "<e2', Es2'> \<mapsto>* <e3, Es3>" by fact then have d3: "<e2, Es2> \<mapsto>* <e3, Es3>" using ih by simp have d2: "<e1, Es1> \<mapsto> <e2, Es2>" by fact show "<e1, Es1> \<mapsto>* <e3, Es3>" using d2 d3 by autoqedtext {* The label ih2 cannot be got rid of in this way, because it is used two lines below and we cannot rearrange them. We can still avoid the label by feeding a sequence of facts into a proof using the "moreover"-chaining mechanism: have "statement_1" \<dots> moreover have "statement_2" \<dots> \<dots> moreover have "statement_n" \<dots> ultimately have "statement" \<dots> In this chain, all "statement_i" can be used in the proof of the final "statement". With this we can simplify our proof further to:*}lemma assumes a: "<e1, Es1> \<mapsto>* <e2, Es2>" and b: "<e2, Es2> \<mapsto>* <e3, Es3>" shows "<e1, Es1> \<mapsto>* <e3, Es3>"using a bproof(induct) case (ms1 e1 Es1) show "<e1, Es1> \<mapsto>* <e3, Es3>" by factnext case (ms2 e1 Es1 e2 Es2 e2' Es2') have ih: "<e2', Es2'> \<mapsto>* <e3, Es3> \<Longrightarrow> <e2, Es2> \<mapsto>* <e3, Es3>" by fact have "<e2', Es2'> \<mapsto>* <e3, Es3>" by fact then have "<e2, Es2> \<mapsto>* <e3, Es3>" using ih by simp moreover have "<e1, Es1> \<mapsto> <e2, Es2>" by fact ultimately show "<e1, Es1> \<mapsto>* <e3, Es3>" by autoqedtext {* While automatic proof procedures in Isabelle are not able to prove statements like "P = NP" assuming usual definitions for P and NP, they can automatically discharge the lemmas we just proved. For this we only have to set up the induction and auto will take care of the rest. This means we can write:*}lemma assumes a: "val t" shows "t \<Down> t"using a by (induct) (auto)lemma assumes a: "t \<Down> t'" shows "val t'"using a by (induct) (auto)lemma assumes a: "<e1, Es1> \<mapsto>* <e2, Es2>" and b: "<e2, Es2> \<mapsto>* <e3, Es3>" shows "<e1, Es1> \<mapsto>* <e3, Es3>"using a b by (induct) (auto)section {* EXERCISE 4 *}text {* The point of the machine is that it simulates the evaluation relation. Therefore we like to prove the following:*}theorem assumes a: "t \<Down> t'" shows "<t, []> \<mapsto>* <t', []>"using a proof (induct) case (e_Lam x t) -- {* no assumptions *} show "<Lam [x].t, []> \<mapsto>* <Lam [x].t, []>" by autonext case (e_App t1 x t t2 v' v) -- {* all assumptions in this case *} have a1: "t1 \<Down> Lam [x].t" by fact have ih1: "<t1, []> \<mapsto>* <Lam [x].t, []>" by fact have a2: "t2 \<Down> v'" by fact have ih2: "<t2, []> \<mapsto>* <v', []>" by fact have a3: "t[x::=v'] \<Down> v" by fact have ih3: "<t[x::=v'], []> \<mapsto>* <v, []>" by fact -- {* your reasoning *} show "<App t1 t2, []> \<mapsto>* <v, []>" sorryqedtext {* Again the automatic tools in Isabelle can discharge automatically of the routine work in these proofs. We can write: *}theorem eval_implies_machines_ctx: assumes a: "t \<Down> t'" shows "<t, Es> \<mapsto>* <t', Es>"using aby (induct arbitrary: Es) (metis eval_to_val machine.intros ms1 ms2 ms3 v_Lam)+corollary eval_implies_machines: assumes a: "t \<Down> t'" shows "<t, []> \<mapsto>* <t', []>"using a eval_implies_machines_ctx by simpsection {* Function Definitions: Filling a Lambda-Term into a Context *}text {* Many functions over datatypes can be defined by recursion on the structure. For this purpose, Isabelle provides "fun". To use it one needs to give a name for the function, its type, optionally some pretty-syntax and then some equations defining the function. Like in "inductive", "fun" expects that more than one such equation is separated by "|".*}fun filling :: "ctx \<Rightarrow> lam \<Rightarrow> lam" ("_\<lbrakk>_\<rbrakk>" [100, 100] 100)where "\<box>\<lbrakk>t\<rbrakk> = t"| "(CAppL E t')\<lbrakk>t\<rbrakk> = App (E\<lbrakk>t\<rbrakk>) t'"| "(CAppR t' E)\<lbrakk>t\<rbrakk> = App t' (E\<lbrakk>t\<rbrakk>)"text {* After this definition Isabelle will be able to simplify statements like: *}lemma shows "(CAppL \<box> (Var x))\<lbrakk>Var y\<rbrakk> = App (Var y) (Var x)" by simpfun ctx_compose :: "ctx \<Rightarrow> ctx \<Rightarrow> ctx" (infixr "\<odot>" 99)where "\<box> \<odot> E' = E'"| "(CAppL E t') \<odot> E' = CAppL (E \<odot> E') t'"| "(CAppR t' E) \<odot> E' = CAppR t' (E \<odot> E')"fun ctx_composes :: "ctxs \<Rightarrow> ctx" ("_\<down>" [110] 110)where "[]\<down> = \<box>" | "(E # Es)\<down> = (Es\<down>) \<odot> E"text {* Notice that we not just have given a pretty syntax for the functions, but also some precedences. The numbers inside the [\<dots>] stand for the precedences of the arguments; the one next to it the precedence of the whole term. This means we have to write (Es1 \<odot> Es2) \<odot> Es3 otherwise Es1 \<odot> Es2 \<odot> Es3 is interpreted as Es1 \<odot> (Es2 \<odot> Es3).*}section {* Structural Inductions over Contexts *}text {* So far we have had a look at an induction over an inductive predicate. Another important induction principle is structural inductions for datatypes. To illustrate structural inductions we prove some facts about context composition, some of which we will need later on.*}subsection {* EXERCISE 5 *}text {* Complete the proof and remove the sorries. *}lemma ctx_compose: shows "(E1 \<odot> E2)\<lbrakk>t\<rbrakk> = E1\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>"proof (induct E1) case Hole show "(\<box> \<odot> E2)\<lbrakk>t\<rbrakk> = \<box>\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>" sorrynext case (CAppL E1 t') have ih: "(E1 \<odot> E2)\<lbrakk>t\<rbrakk> = E1\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>" by fact show "((CAppL E1 t') \<odot> E2)\<lbrakk>t\<rbrakk> = (CAppL E1 t')\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>" sorrynext case (CAppR t' E1) have ih: "(E1 \<odot> E2)\<lbrakk>t\<rbrakk> = E1\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>" by fact show "((CAppR t' E1) \<odot> E2)\<lbrakk>t\<rbrakk> = (CAppR t' E1)\<lbrakk>E2\<lbrakk>t\<rbrakk>\<rbrakk>" sorryqedsubsection {* EXERCISE 6 *}text {* Remove the sorries in the ctx_append proof below. You can make use of the following two properties. *}lemma neut_hole: shows "E \<odot> \<box> = E"by (induct E) (simp_all)lemma compose_assoc: shows "(E1 \<odot> E2) \<odot> E3 = E1 \<odot> (E2 \<odot> E3)"by (induct E1) (simp_all)lemma shows "(Es1 @ Es2)\<down> = (Es2\<down>) \<odot> (Es1\<down>)"proof (induct Es1) case Nil show "([] @ Es2)\<down> = Es2\<down> \<odot> []\<down>" sorrynext case (Cons E Es1) have ih: "(Es1 @ Es2)\<down> = Es2\<down> \<odot> Es1\<down>" by fact show "((E # Es1) @ Es2)\<down> = Es2\<down> \<odot> (E # Es1)\<down>" sorryqedtext {* The last proof involves several steps of equational reasoning. Isar provides some convenient means to express such equational reasoning in a much cleaner fashion using the "also have" construction. *}lemma shows "(Es1 @ Es2)\<down> = (Es2\<down>) \<odot> (Es1\<down>)"proof (induct Es1) case Nil show "([] @ Es2)\<down> = Es2\<down> \<odot> []\<down>" using neut_hole by simpnext case (Cons E Es1) have ih: "(Es1 @ Es2)\<down> = Es2\<down> \<odot> Es1\<down>" by fact have "((E # Es1) @ Es2)\<down> = (E # (Es1 @ Es2))\<down>" by simp also have "\<dots> = (Es1 @ Es2)\<down> \<odot> E" by simp also have "\<dots> = (Es2\<down> \<odot> Es1\<down>) \<odot> E" using ih by simp also have "\<dots> = Es2\<down> \<odot> (Es1\<down> \<odot> E)" using compose_assoc by simp also have "\<dots> = Es2\<down> \<odot> (E # Es1)\<down>" by simp finally show "((E # Es1) @ Es2)\<down> = Es2\<down> \<odot> (E # Es1)\<down>" by simpqedend