I am new to theorem proving and Isabelle. I am trying to prove a simple(?) theorem in Isabelle about lists.
Here is the theory:
theory Scratch
imports
Main
Option
String
begin
fun list_difference :: "string list => string list => nat"
where
"list_difference [] [] = 0"
| "list_difference [] x = length x"
| "list_difference x [] = length x"
| "list_difference (x#d1) (y#d2) = (if (x=y) then list_difference d1 d2 else (1 + list_difference d1 d2))"
fun modify :: "string list ⇒ nat ⇒ string list"
where
"modify list n = list[n:=''somethingnew'']"
These are the supporting lemmas
lemma diff_zero [simp]:
shows "list_difference somelist somelist = 0"
apply(induct_tac somelist, auto)
done
lemma sub1 [simp]:
shows "modify [] 0 = []"
apply(auto)
done
lemma diff_zero_basecase [simp]:
shows "list_difference somelist (modify somelist 0) <= 1"
apply(induct_tac somelist, auto)
done
This the original theorem I am trying to prove
(*Description : modify will change only one or zero elements.. so diff should be <= 1*) <br>
lemma modification_lemma [simp]:
shows "list_difference somelist (modify somelist index) ≤ 1"
apply(induct_tac somelist, auto)
apply(cases index, auto)
oops
How do I proceed to prove this theorem?
My other question is how to proceed from these kind of situations generally when trying to prove theorems? I tried following Isabelle tutorials but I was not able to get general advice on this.
I give you a proof, and explain some basic techniques I've used to get results, but in a fairly mindless way. Someone else may want to provide an explanation that will give you a better understanding of the subject matter: induction involving nat and list.
I shortened some identifiers, and I put the whole theory in below. There are basically two free variables that are in the formulas that need proving, ls and idx.
So, for proof goals, in trying things, I was doing a lot of what I show next, or some variation, with maybe cases for induct:
(*MINDLESS_TECHNIQUE_1*)
apply(induct ls, auto)
apply(induct idx, auto)
At a certain point, auto was taking a long time, which meant it was maybe looping bad because of the simp rules you had in.
I eliminated the simp rules and inserted them only where needed, which got rid of auto looping.
That was the first point of progress, which got me to MINDLESS_TECHNIQUE_2, breaking out proof goals into a lemma, to be used with MINDLESS_TECHNIQUE_1, along with Sledgehammer.
At this point, for your modification_lemma, after using apply like above, I had 3 proof goals, and I used Sledgehammer to prove two of them. I then broke out the 3rd proof goal as shown below, which was easily proved with by(induct ls, auto).
Update: Using Alexander's tip to get to the same proof goal as the broke-out lemma above, I've added a proof, in the source below, where I unwrap the two bound variables. It's still an ugly solution, so maybe there's a better way than ending up with bound variables.
Using auto or simp_all as I do, the reason I can't mindlessly get the final result with by(induct ls, auto) is because variables idx and ls are bound by a meta-all, !!. They need to be unwrapped if no auto tool can do the proof-job (I guess), which clutters things up a lot.
theory c2
imports Main Option String
begin
fun list_diff :: "string list => string list => nat" where
"list_diff [] [] = 0"
|"list_diff [] x = length x"
|"list_diff x [] = length x"
|"list_diff (x#d1) (y#d2) =
(if (x=y) then list_diff d1 d2 else (1 + list_diff d1 d2))"
fun modify :: "string list => nat => string list" where
"modify ls n = ls[n := ''abc'']"
lemma diff_zero:
"list_diff ls ls = 0"
by(induct_tac ls, auto)
lemma sub1:
"modify [] 0 = []"
by(auto)
lemma diff_zero_basecase:
"list_diff ls (modify ls 0) <= 1"
by(induct_tac ls, auto simp add: diff_zero)
(*****************************************************************************)
lemma the_mindless_result_of_eliminating_simp_rules_and_breaking_out_goals:
"(!!a ls. list_diff ls (ls[idx := ''abc'']) <= Suc 0 ==>
list_diff (a # ls)
(case idx of 0 => ''abc'' # ls | Suc j => a # ls[j := ''abc'']) <= Suc 0)
==> list_diff ls (ls[Suc idx := ''abc'']) <= Suc 0 ==>
list_diff ls (ls[idx := ''abc'']) <= Suc 0"
by(induct ls, auto)
lemma modification_lemma:
"list_diff ls (modify ls idx) <= 1"
apply(induct ls, auto)
apply(induct idx, auto)
apply(metis diff_zero le0)
apply(metis diff_zero) (*The goal I broke out to the above lemma.*)
by (metis the_mindless_result_of_eliminating_simp_rules_and_breaking_out_goals)
(*****************************************************************************)
(*Update: After the third 'apply', I can't mindlessly do 'by(induct ls, auto)',
because variables 'ls' and 'idx' are bound by '!!. If I use 'auto' like I
did, and I don't want to break out the proof goal, I need to unwrap the
variables 'idx' and 'ls, as shown below.*)
lemma unwrapping_variables:
"list_diff ls (modify ls idx) <= 1"
apply(induct ls, simp_all)
apply(induct idx, simp_all)
apply(simp_all add: diff_zero)
proof-
fix idx
show "!!ls. ((!!a ls. list_diff ls (ls[idx := ''abc'']) <= Suc 0 ==>
list_diff (a # ls)
(case idx of 0 => ''abc'' # ls | Suc j => a # ls[j := ''abc'']) <= Suc 0)
==> list_diff ls (ls[Suc idx := ''abc'']) <= Suc 0 ==>
list_diff ls (ls[idx := ''abc'']) <= Suc 0)"
proof-
fix ls
show "(!!a ls. list_diff ls (ls[idx := ''abc'']) <= Suc 0 ==>
list_diff (a # ls)
(case idx of 0 => ''abc'' # ls | Suc j => a # ls[j := ''abc'']) <= Suc 0)
==> list_diff ls (ls[Suc idx := ''abc'']) <= Suc 0 ==>
list_diff ls (ls[idx := ''abc'']) <= Suc 0"
by(induct ls, auto)
qed
qed
thm unwrapping_variables
end
Related
I have datatype stack_op which consists of several (~20) cases. I'm trying write function which skips some of that cases in list:
function (sequential) skip_expr :: "stack_op list ⇒ stack_op list" where
"skip_expr [] = []"
| "skip_expr ((stack_op.Unary _)#other) = (skip_expr other)"
| "skip_expr ((stack_op.Binary _)#other) = skip_expr (skip_expr other)"
| "skip_expr ((stack_op.Value _)#other) = other"
| "skip_expr other = other"
by pat_completeness auto termination by lexicographic_order
which seems to always terminate. But trying by lexicographic order generates such unresolved cases:
Calls:
c) stack_op.Binary uv_ # other ~> skip_expr other
Measures:
1) size_list size
2) length
Result matrix:
1 2
c: ? ?
(size_change also desn't work)
I've read https://isabelle.in.tum.de/dist/Isabelle2021/doc/functions.pdf, but it couldn't help. (Maybe there are more complex examples of tremination use?)
I tried to rewrite function adding another param:
function (sequential) skip_expr :: "stack_op list ⇒ nat ⇒ stack_op list" where
"skip_expr l 0 = l"
| "skip_expr [] _ = []"
| "skip_expr ((stack_op.Unary _)#other) depth = (skip_expr other (depth - 1))"
| "skip_expr ((stack_op.Binary _)#other) depth =
(let buff1 = (skip_expr other (depth - 1))
in (skip_expr buff1 (length buff1)))"
| "skip_expr ((stack_op.Value _)#other) _ = other"
| "skip_expr other _ = other"
by pat_completeness auto
termination by (relation "measure (λ(_,dep). dep)") auto
which generates unresolved subgoal:
1. ⋀other v. skip_expr_dom (other, v) ⟹ length (skip_expr other v) < Suc v
which I also don't how to proof.
Could anyone how such cases solved (As I can understand there is some problem with two-level recursive call on rigth side of stack_op.Binary case)? Or maybe there is another way to make such skip?
Thanks in advance
The lexicographic_order method simply tries to solve the arising goals with the simplifier, so if the simplifier gets stuck you end up with unresolved termination subgoals.
In this case, as you identified correctly, the problem is that you have a nested recursive call skip_expr (skip_expr other). This is always problematic because at this stage, the simplifier knows nothing about what skip_expr does to the input list. For all we know, it might just return the list unmodified, or even a longer list, and then it surely would not terminate.
Confronting the issue head on
The solution is to show something about length (skip_expr …) and make that information available to the simplifier. Because we have not yet shown termination of the function, we have to use the skip_expr.psimps rules and the partial induction rule skip_expr.pinduct, i.e. every statement we make about skip_expr xs always has as a precondition that skip_expr actually terminates on the input xs. For this, there is the predicate skip_expr_dom.
Putting it all together, it looks like this:
lemma length_skip_expr [termination_simp]:
"skip_expr_dom xs ⟹ length (skip_expr xs) ≤ length xs"
by (induction xs rule: skip_expr.pinduct) (auto simp: skip_expr.psimps)
termination skip_expr by lexicographic_order
Circumventing the issue
Sometimes it can also be easier to circumvent the issue entirely. In your case, you could e.g. define a more general function skip_exprs that skips not just one instruction but n instructions. This you can define without nested induction:
fun skip_exprs :: "nat ⇒ stack_op list ⇒ stack_op list" where
"skip_exprs 0 xs = xs"
| "skip_exprs (Suc n) [] = []"
| "skip_exprs (Suc n) (Unary _ # other) = skip_exprs (Suc n) other"
| "skip_exprs (Suc n) (Binary _ # other) = skip_exprs (Suc (Suc n)) other"
| "skip_exprs (Suc n) (Value _ # other) = skip_exprs n other"
| "skip_exprs (Suc n) xs = xs"
Equivalence to your skip_expr is then straightforward to prove:
lemma skip_exprs_conv_skip_expr: "skip_exprs n xs = (skip_expr ^^ n) xs"
proof -
have [simp]: "(skip_expr ^^ n) [] = []" for n
by (induction n) auto
have [simp]: "(skip_expr ^^ n) (Other # xs) = Other # xs" for xs n
by (induction n) auto
show ?thesis
by (induction n xs rule: skip_exprs.induct)
(auto simp del: funpow.simps simp: funpow_Suc_right)
qed
lemma skip_expr_Suc_0 [simp]: "skip_exprs (Suc 0) xs = skip_expr xs"
by (simp add: skip_exprs_conv_skip_expr)
In your case, I don't think it actually makes sense to do this because figuring out the termination is fairly easy, but it may be good to keep in mind.
There is a set of some structures. I'm trying to prove that the cardinality of the set equals some number. Full theory is too long to post here. So here is a simplified one just to show the idea.
Let the objects (which I need to count) are sets containing natural numbers from 1 to n. The idea of the proof is as follows. I define a function which transforms sets to lists of 0 and 1. Here is the function and its inverse:
fun set_to_bitmap :: "nat set ⇒ nat ⇒ nat ⇒ nat list" where
"set_to_bitmap xs x 0 = []"
| "set_to_bitmap xs x (Suc n) =
(if x ∈ xs then Suc 0 else 0) # set_to_bitmap xs (Suc x) n"
fun bitmap_to_set :: "nat list ⇒ nat ⇒ nat set" where
"bitmap_to_set [] n = {}"
| "bitmap_to_set (x#xs) n =
(if x = Suc 0 then {n} else {}) ∪ bitmap_to_set xs (Suc n)"
value "set_to_bitmap {1,3,7,8} 1 8"
value "bitmap_to_set (set_to_bitmap {1,3,7,8} 1 8) 1"
Then I plan to prove that 1) a number of 0/1 lists with length n equals 2^^n,
2) the functions are bijections,
3) so the cardinality of the original set is 2^^n too.
Here are some auxiliary definitions and lemmas, which seems useful:
definition "valid_set xs n ≡ (∀a. a ∈ xs ⟶ 0 < a ∧ a ≤ n)"
definition "valid_bitmap ps n ≡ length ps = n ∧ set ps ⊆ {0, Suc 0}"
lemma length_set_to_bitmap:
"valid_set xs n ⟹
x = Suc 0 ⟹
length (set_to_bitmap xs x n) = n"
apply (induct xs x n rule: set_to_bitmap.induct)
apply simp
sorry
lemma bitmap_members:
"valid_set xs n ⟹
x = Suc 0 ⟹
set_to_bitmap xs x n = ps ⟹
set ps ⊆ {0, Suc 0}"
apply (induct xs x n arbitrary: ps rule: set_to_bitmap.induct)
apply simp
sorry
lemma valid_set_to_valid_bitmap:
"valid_set xs n ⟹
x = Suc 0 ⟹
set_to_bitmap xs x n = ps ⟹
valid_bitmap ps n"
unfolding valid_bitmap_def
using bitmap_members length_set_to_bitmap by auto
lemma valid_bitmap_to_valid_set:
"valid_bitmap ps n ⟹
x = Suc 0 ⟹
bitmap_to_set ps x = xs ⟹
valid_set xs n"
sorry
lemma set_to_bitmap_inj:
"valid_set xs n ⟹
valid_set xy n ⟹
x = Suc 0 ⟹
set_to_bitmap xs x n = ps ⟹
set_to_bitmap ys x n = qs ⟹
ps = qs ⟹
xs = ys"
sorry
lemma set_to_bitmap_surj:
"valid_bitmap ps n ⟹
x = Suc 0 ⟹
∃xs. set_to_bitmap xs x n = ps"
sorry
lemma bitmap_to_set_to_bitmap_id:
"valid_set xs n ⟹
x = Suc 0 ⟹
bitmap_to_set (set_to_bitmap xs x n) x = xs"
sorry
lemma set_to_bitmap_to_set_id:
"valid_bitmap ps n ⟹
x = Suc 0 ⟹
set_to_bitmap (bitmap_to_set ps x) x n = ps"
sorry
Here is a final lemma:
lemma valid_set_size:
"card {xs. valid_set xs n} = 2 ^^ n"
Does this approach seem valid? Are there any examples of such a proof? Could you suggest an idea on how to prove the lemmas? I'm stuck because the induction with set_to_bitmap.induct seems to be not applicable here.
In principle, that kind of approach does work: if you have a function f from a set A to a set B and an inverse function to it, you can prove bij_betw f A B (read: f is a bijection from A to B), and that then implies card A = card B.
However, there are a few comments that I have:
You should use bool lists instead of nat lists if you can only have 0 or 1 in them anyway.
It is usually better to use existing library functions than to define new ones yourself. Your two functions could be defined using library functions like this:
set_to_bitmap :: nat ⇒ nat ⇒ nat set ⇒ bool list
set_to_bitmap x n A = map (λi. i ∈ A) [x..<x+n]
bitmap_to_set :: nat ⇒ bool list ⇒ nat set
bitmap_to_set n xs = (λi. i + n) ` {i. i < length xs ∧ xs ! i}```
Side note: I would use upper-case letters for sets, not something like xs (which is usually used for lists).
Perhaps this is because you simplified your problem, but in its present form, valid_set A n is simply the same as A ⊆ {1..n} and the {A. valid_set A n} is simply Pow {1..n}. The cardinality of that is easy to show with results from the library:
lemma "card (Pow {1..(n::nat)}) = 2 ^ n"
by (simp add: card_Pow)`
As for your original questions: Your first few lemmas are provable, but for the induction to go through, you have to get rid of some of the unneeded assumptions first. The x = Suc 0 is the worst one – there is no way you can use induction if you have that as an assumption, because as soon as you do one induction step, you increase x by 1 and so you won't be able to apply your induction hypothesis. The following versions of your first three lemmas go through easily:
lemma length_set_to_bitmap:
"length (set_to_bitmap xs x n) = n"
by (induct xs x n rule: set_to_bitmap.induct) auto
lemma bitmap_members:
"set (set_to_bitmap xs x n) ⊆ {0, Suc 0}"
by (induct xs x n rule: set_to_bitmap.induct) auto
lemma valid_set_to_valid_bitmap: "valid_bitmap (set_to_bitmap xs x n) n"
unfolding valid_bitmap_def
using bitmap_members length_set_to_bitmap by auto
I also recommend not adding "abbreviations" like ps = set_to_bitmap xs x n as an assumption. It doesn't break anything, but it tends to complicate things needlessly.
The next lemma is a bit trickier. Due to your recursive definitions, you have to generalise the lemma first (valid_bitmap requires the set to be in the range from 1 to n, but once you make one induction step it has to be from 2 to n). The following works:
lemma valid_bitmap_to_valid_set_aux:
"bitmap_to_set ps x ⊆ {x..<x + length ps}"
by (induction ps x rule: bitmap_to_set.induct)
(auto simp: valid_bitmap_def valid_set_def)
lemma valid_bitmap_to_valid_set:
"valid_bitmap ps n ⟹ valid_set (bitmap_to_set ps 1) n"
using valid_bitmap_to_valid_set_aux unfolding valid_bitmap_def valid_set_def
by force
Injectivity and surjectivity (which is your ultimate goal) should follow from the fact that the two are inverse functions. Proving that will probably be doable with induction, but will require a few generalisations and auxiliary lemmas. It should be easier if you stick to the non-recursive definition using library functions that I sketched above.
Consider the following datatypes with bindings in Nominal Isabelle:
theory Example
imports "Nominal2.Nominal2"
begin
atom_decl vrs
nominal_datatype ty =
Tvar "vrs"
| Arrow x::vrs T::"ty" binds x in T
nominal_datatype trm =
Var "vrs"
| Abs x::"vrs" t::"trm" binds x in t
inductive
typing :: "trm ⇒ ty ⇒ bool" ("_ , _" [60,60] 60)
where
T_Abs[intro]: "(Abs x t) , (Arrow x T)"
equivariance typing
nominal_inductive typing done
lemma
assumes "(Abs x t), (Arrow y T)"
shows "x = y"
using assms
I want to prove that the two bindings appearing in the relation are equal. I see two ways an Isabelle user could help:
If you know Nominal Isabelle is it possible to do this?
Otherwise, are the two occurrences of x in the rule T_Abs equal for the assistant or are they sort of bound variable with different identity?
If you know Nominal Isabelle is it possible to do this?
Unfortunately, it is not possible to prove the theorem that you are trying to prove. Here is a counterexample (the proofs were Sledgehammered):
theory Scratch
imports "Nominal2.Nominal2"
begin
atom_decl vrs
nominal_datatype ty =
Tvar "vrs"
| Arrow x::vrs T::"ty" binds x in T
nominal_datatype trm =
Var "vrs"
| Abs x::"vrs" t::"trm" binds x in t
inductive
typing :: "trm ⇒ ty ⇒ bool" ("_ , _" [60,60] 60)
where
T_Abs[intro]: "(Abs x t) , (Arrow x T)"
equivariance typing
nominal_inductive typing .
abbreviation s where "s ≡ Sort ''Scratch.vrs'' []"
abbreviation v where "v n ≡ Abs_vrs (Atom s n)"
lemma neq: "Abs (v 1) (Var (v 0)), Arrow (v (Suc (Suc 0))) (Tvar (v 0))"
(is "?a, ?b")
proof-
have a_def: "Abs (v 1) (Var (v 0)) = Abs (v (Suc (Suc 0))) (Var (v 0))"
(*Sledgehammered*)
by simp (smt Abs_vrs_inverse atom.inject flip_at_base_simps(3) fresh_PairD(2)
fresh_at_base(2) mem_Collect_eq nat.distinct(1) sort_of.simps trm.fresh(1))
from typing.simps[of ?a ?b, unfolded this, THEN iffD2] have
"Abs (v (Suc (Suc 0))) (Var (v 0)) , Arrow (v (Suc (Suc 0))) (Tvar (v 0))"
by auto
then show ?thesis unfolding a_def by clarsimp
qed
lemma "∃x y t T. x ≠ y ∧ (Abs x t), (Arrow y T)"
proof(intro exI conjI)
show "v 1 ≠ v (Suc (Suc 0))"
(*Sledgehammered*)
by (smt Abs_vrs_inverse One_nat_def atom.inject mem_Collect_eq n_not_Suc_n
sort_of.simps)
show "Abs (v 1) (Var (v 0)) , Arrow (v (Suc (Suc 0))) (Tvar (v 0))"
by (rule neq)
qed
end
Otherwise, are the two occurrences of x in the rule T_Abs equal for
the assistant or are they sort of bound variable with different
identity?
I believe that you are thinking along the right lines and, hopefully, the example above will clarify any confusion that you might have. Generally, you could interpret the meaning of Abs x t1 = Abs y t2 as the alpha-equivalence of (λx. t1) and (λy. t2). Of course, (λx. t1) and (λy. t2) may be alpha equivalent without x and y being equal.
This pattern generator produces a list with a given number at a given position, all other values are zero.
fun pattern_one_value :: "nat ⇒ nat ⇒ nat ⇒ nat ⇒ nat list" where
"pattern_one_value _ _ _ 0 = []" |
"pattern_one_value pos pos1 val lng =
(if pos = pos1 then val else 0) # (pattern_one_value pos (pos1 + 1) val (lng - 1))"
The following lemma is aimed to prove that generated lists contain the right value at the given position.
lemma pattern_one_value_check [simp]: "∀pos val. pos < lng ⟹ pattern_one_value pos 0 val lng ! pos = val"
proof(induct lng)
case 0 then show ?case by simp
next
case (Suc lng) then show ?case by auto
qed
It seems to be a correct proof; however, changing val in the cons expression of the generator function into an arbitrary number like (if pos = pos1 then 7 else 0) # ..., the proof still holds because both the base and the induction hypothesis are false.
Where am I wrong? Thanks for any help.
It seems to be a correct proof; however, changing val in the cons
expression of the generator function into an arbitrary number like (if pos = pos1 then 7 else 0) # ..., the proof still holds because both
the base and the induction hypothesis are false. Where am I wrong?
I believe that the problem is related to an attempt to treat HOL's universal quantifier ∀ as equivalent to Pure's universal quantifier ⋀. Effectively, it is possible to prove anything from the premise of the theorem pattern_one_value_check, as stated in your question. Indeed:
lemma pattern_one_value_check'[simp]:
"(∀pos val::nat. pos < (lng::nat)) = False"
by auto
lemma pattern_one_value_check''[simp]:
"(∀pos val::nat. pos < (lng::nat)) ⟹ P"
by auto
I believe that you meant to use Pure's universal quantification in the statement of the theorem, e.g.
lemma pattern_one_value_check [simp]:
"⋀pos val. pos < lng ⟹ pattern_one_value pos 0 val lng ! pos = val"
proof(induct lng)
case 0 then show ?case by simp
next
case (Suc lng) then show ?case sorry
qed
In fact, even this is not necessary. The following theorem, once proven, will appear in the context as identical to the one stated above:
lemma pattern_one_value_check' [simp]:
"pos < lng ⟹ pattern_one_value pos 0 val lng ! pos = val"
proof(induct lng)
case 0 then show ?case by simp
next
case (Suc lng) then show ?case sorry
qed
If you seek a more detailed explanation, see Section 2.1 in Isar-ref and the document "Programming and Proving in Isabelle/HOL", both are part of the official documentation.
As a side note, I have to mention that, perhaps, there is an easier way to define pattern_one_value. In this case, the proof of pattern_one_value_check also seems to be easier:
definition pattern_one_value :: "nat ⇒ nat ⇒ nat ⇒ nat list"
where "pattern_one_value val pos len = list_update (replicate len 0) pos val"
lemma pattern_one_value_check:
assumes "pos < len"
shows "pattern_one_value val pos len ! pos = val"
using assms unfolding pattern_one_value_def
apply(induct len)
subgoal by auto
subgoal by (metis length_replicate nth_list_update)
done
I am not sure, but I think sometimes my proofs would be easier if I had a predecessor function, e.g., in case a variable is known not to be zero.
I don't know a good example, but perhaps here: { fix n have "(n::nat) > 0 ⟹ (∑i<n. f i) = Predecessor n" sorry }
Possibly because it is not a good idea, there is no predecessor function in the library.
Is there a way to simulate a predecessor function or similar?
I have thought of this example:
theorem dummy:
shows "1=1" (* dummy *)
proof-
(* Predecessor function *)
def pred == "λnum::nat. (∑i∈{ i . Suc i = num}. i)"
{fix n :: nat
from pred_def have "n>0 ⟹ Suc (pred n) = n"
apply(induct n)
by simp_all
}
show ?thesis sorry
qed
Your definition is unnecessarily complicated. Why do you not just write
def pred ≡ "λn::nat. n - 1"
Then you can have
have [simp]: "⋀n. n > 0 ⟹ Suc (pred n) = n" by (simp add: pred_def)
In the case of 0, the pred function then simply returns 0 and Suc (pred 0) = 0 obviously doesn't hold. You could also define pred ≡ "λn. THE n'. Suc n' = n". That would return the unique natural number whose successor is n if such a number exists (i.e. if n > 0) and undefined (i.e. some natural number you know nothing about) otherwise. However, I would argue that in this case, it is much easier and sensible to just do pred ≡ λn::nat. n - 1.
I would suspect that in most cases, you can simply forgo the pred function and write n - 1; however, I do know that it is sometimes good to have the - 1 “protected” by a definition. In these cases, I usually def a variable n' as n - 1 and prove Suc n' = n – basically the same thing. In my opinion, seeing as proving this takes only one line, it does not really merit a definition of its own, such as this pred function, but one could make a reasonable case for it, I guess.
Another thing: I've noticed you use lemma "1 = 1" as some kind of dummy environment to do Isar proofs in. I would like to point out the existence of notepad, which exists precisely for that use case and that can be used as follows:
notepad
begin
have "some fact" by something
end