What do the double brackets (and semicolon) stand for and how can I type them in jEdit?
lemma "[[ xs # zs = ys # xs; [] # xs = [] # [] ]] ==> ys = zs"
apply simp
done
Pasting this to jedit directly yields
Inner syntax error⌂
Failed to parse prop
but I see this being used Tutorial on Isabelle/HOL in https://isabelle.in.tum.de/dist/Isabelle2022/doc/tutorial.pdf section 3.1.5 Assumptions.
This is a shorthand notation for
lemma "xs # zs = ys # xs ==> [] # xs = [] # [] ==> ys = zs"
apply simp
done
in general
[| P1; P2; ... ; Pn |] ==> C
denotes
P1 ==> P2 ==> ... ==> Pn ==> C
This is explained in Programming and Proving in Isabelle/HOL https://isabelle.in.tum.de/dist/Isabelle2022/doc/prog-prove.pdf at the end of section 2.1.1 Types, Terms and Formulas
To display implications in this style in Isabelle/jEdit you need to set Plugins >
Plugin Options > Isabelle/General > Print Mode to “brackets” and restart.
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.
I'm currently trying use Isabelle/HOL's reification tactic. I'm unable to use different interpretation functions below quantifiers/lambdas. The below MWE illustrates this. The important part is the definition of the form function, where the ter call occurs below the ∀. When trying to use the reify tactic I get an Cannot find the atoms equation error. I don't get this error for interpretation functions which only call themselves under quantifiers.
I can't really reformulate my problem to avoid this. Does anybody know how to get reify working for such cases?
theory MWE
imports
"HOL-Library.Reflection"
begin
datatype Ter = V nat | P Ter Ter
datatype Form = All0 Ter
fun ter :: "Ter ⇒ nat list ⇒ nat"
where "ter (V n) vs = vs ! n"
| "ter (P t1 t2) vs = ter t1 vs + ter t2 vs"
fun form :: "Form ⇒ nat list ⇒ bool"
where "form (All0 t) vs = (∀ v . ter t (v#vs) = 0)" (* use of different interpretation function below quantifier *)
(*
I would expect this to reify to:
form (All0 (P (V 0) (V 0))) []
instead I get an error :-(
*)
lemma "∀ n :: nat . n + n = 0"
apply (reify ter.simps form.simps)
(* proof (prove)
goal (1 subgoal):
1. ∀n. n + n = n + n
Cannot find the atoms equation *)
oops
(* As a side note: the following example in src/HOL/ex/Reflection_Examples.thy (line 448, Isabelle2022) seems to be broken? For me, the reify invocation
doesn't change the goal at all. It uses quantifiers too, but only calls the same interpretation function under quantifiers and also doesn't throw an error,
so at least for me this seems to be unrelated to my problem.
*)
(*
lemma " ∀x. ∃n. ((Suc n) * length (([(3::int) * x + f t * y - 9 + (- z)] # []) # xs) = length xs) ∧ m < 5*n - length (xs # [2,3,4,x*z + 8 - y]) ⟶ (∃p. ∀q. p ∧ q ⟶ r)"
apply (reify Irifm.simps Irnat_simps Irlist.simps Irint_simps)
oops
*)
end
I am doing Exercise 2.6 from the Concrete Semantics book:
Starting from the type 'a tree defined in the text, define a function contents :: 'a tree ⇒ 'a list that collects all values in a tree in a list, in any order, without removing duplicates. Then define a function sum_tree :: nat tree ⇒ nat that sums up all values in a tree of natural numbers and prove sum_tree t = sum_list (contents t) (where sum_list is predefined).
I have started to prove the theorem not using auto but guiding Isabelle to use the necessary theorems:
theory Minimal
imports Main
begin
datatype 'a tree = Tip | Node "'a tree" 'a "'a tree"
fun contents :: "'a tree ⇒ 'a list" where
"contents Tip = []"
| "contents (Node l a r) = a # (contents l) # (contents r)"
fun sum_tree :: "nat tree ⇒ nat" where
"sum_tree Tip = 0"
| "sum_tree (Node l a r) = a + (sum_tree l) + (sum_tree r)"
lemma sum_list_contents:
"sum_list (contents t1) + sum_list (contents t2) = sum_list (contents t1 # contents t2)"
apply auto
done
lemma sum_commutes: "sum_tree(t) = sum_list(contents(t))"
apply (induction t)
apply (simp only: sum_tree.simps contents.simps sum_list.Nil)
apply (simp only: sum_list.Cons contents.simps sum_tree.simps sum_list_contents)
Here it arrives to a proof state
proof (prove)
goal (1 subgoal):
1. ⋀t1 x2 t2.
sum_tree t1 = sum_list (contents t1) ⟹
sum_tree t2 = sum_list (contents t2) ⟹
x2 + sum_list (contents t1) + sum_list (contents t2) = x2 + sum_list (contents t1 # contents t2)
Where I wonder why simp did not use the provided sum_list_contents lemma. I know simple simp would solve the equation.
What does general simp contain that simp only would not use in this case?
As pointed out in the comments, the missing piece is associativity of addition for natural numbers. Adding add.assoc to the simpplification rules solves the equation.
Alternatively, the order of operands when defining the tree sum could be changed:
fun sum_tree_1 :: "nat tree ⇒ nat" where
"sum_tree_1 Tip = 0"
| "sum_tree_1 (Node l a r) = a + ((sum_tree_1 l) + (sum_tree_1 r))"
Then the associativity is not required:
lemma sum_commutes_1: "sum_tree_1(t) = sum_list(contents(t))"
apply (induction t)
apply (simp only: sum_tree_1.simps contents.simps sum_list.Nil)
apply (simp only: sum_list.Cons contents.simps sum_tree_1.simps sum_list_contents)
done
In the example below, I want to use simp to prove that some terms from
simply typed lambda calculus typecheck.
I add each typechecking rule as a rewriting rule for simp, so simp performs
conditional rewrites and creates schematic variables along the way.
However, while rewriting the side conditions for some rewrites, simp gets
stuck on rewriting terms involving schematic variables, because it does not
instantiate them:
theory Stlc imports Main
begin
type_synonym var = string
datatype exp =
Var var
| Const nat
| Plus exp exp
| Abs var exp
| App exp exp
datatype type =
Nat |
Fun type type
type_synonym ('k, 'v) fmap = "'k ⇒ 'v option"
definition lookup :: "('k, 'v) fmap ⇒ 'k ⇒ 'v option" where
"lookup m x = m x"
definition add :: "('k, 'v) fmap ⇒ 'k ⇒ 'v ⇒ ('k, 'v) fmap" where
"add m x a = (λy. if y = x then Some a else m y)"
definition empty :: "('k, 'v) fmap" where
"empty = (λy. None)"
notation
lookup (infix "$?" 60) and
add ("_ $+ '( _ ', _ ')") and
empty ("$0")
inductive hasty :: "(var, type) fmap ⇒ exp ⇒ type ⇒ bool" where
HtVar:
"G $? x = Some t
⟹ hasty G (Var x) t" |
HtConst:
"hasty G (Const n) Nat" |
HtPlus:
"⟦ hasty G e1 Nat;
hasty G e2 Nat ⟧
⟹ hasty G (Plus e1 e2) Nat" |
HtAbs:
"hasty (G $+ (x, t1)) e1 t2
⟹ hasty G (Abs x e1) (Fun t1 t2)" |
HtApp:
"⟦ hasty G e1 (Fun t1 t2);
hasty G e2 t1 ⟧
⟹ hasty G (App e1 e2) t2"
named_theorems my_simps "simplification rules for typechecking"
declare HtVar [my_simps]
declare HtConst [my_simps]
declare HtPlus [my_simps]
declare HtAbs [my_simps]
declare HtApp [my_simps]
declare lookup_def [my_simps]
declare add_def [my_simps]
lemma "hasty $0 (Plus (Const 1) (Const 1)) Nat"
using [[simp_trace_new mode=full]]
apply(simp add: my_simps)
done
lemma "hasty $0 (Abs ''n'' (Abs ''m'' (Plus (Var ''n'') (Var ''m''))))
(Fun Nat (Fun Nat Nat))"
apply (simp add: my_simps)
done
lemma "⟦P ∧ Q ⟧ ⟹ Q"
apply (rule conjE)
apply(simp) (* note: this simp step does instantiate schematic variables *)
apply assumption
done
(* but here, it seems that simp does not instantiate schematic variables: *)
lemma eleven: "hasty $0 (App (App
(Abs ''n'' (Abs ''m'' (Plus (Var ''n'') (Var ''m''))))
(Const 7)) (Const 4)) Nat"
using [[simp_trace_new mode=full]]
apply (simp add: my_simps) (* seems to fail on unifying "?t1.3 = type.Nat" *)
The relevant part of the simplifier trace (I guess) is the following:
Apply rewrite rule?
Instance of Option.option.inject: Some ?t1.3 = Some type.Nat ≡ ?t1.3 = type.Nat
Trying to rewrite: Some ?t1.3 = Some type.Nat
Successfully rewrote
Some ?t1.3 = Some type.Nat ≡ ?t1.3 = type.Nat
Step failed
In an instance of Stlc.hasty.HtVar:
(λy. if y = ''m'' then Some ?t1.1 else if y = ''n'' then Some ?t1.3 else $0 y) $? ''n'' = Some type.Nat ⟹
hasty (λy. if y = ''m'' then Some ?t1.1 else if y = ''n'' then Some ?t1.3 else $0 y) (Var ''n'') type.Nat ≡ True
Was trying to rewrite:
hasty (λy. if y = ''m'' then Some ?t1.1 else if y = ''n'' then Some ?t1.3 else $0 y) (Var ''n'') type.Nat
Just before the failing step, rewriting stops at ?t1.3 = type.Nat.
However, I would like ?t1.3 = type.Nat to be rewritten to True, and
?t1.3 be instantiated to type.Nat along the way.
How can I achieve this?
Isabelle's simplifier on its own never instantiates any schematic variables in the goal. This is only done by the so-called solvers. for example, the solver HOL unsafe tries among others the tactics rule refl and assumption. This is why the example with ⟦P ∧ Q ⟧ ⟹ Q works with simp.
For solving the assumptions of conditional rewrite rules like HtVar, the subgoaler also plays a role. The subgoaler determines how the conditions should be solved. By default in HOL, this is asm_simp_tac, i.e., the equivalent to the method simp (no_asm_simp). This subgoaler cannot handle the instantiation of schematics in the assumption. You can see this by enabling the other simplifier trace:
using [[simp_trace]] supply [[simp_trace_depth_limit=10]]
apply (simp add: my_simps)
gives the following trace message:
[6]Proved wrong theorem (bad subgoaler?)
hasty (λy. if y = ''m'' then Some ?t1.1 else if y = ''n'' then Some type.Nat else $0 y) (Var ''n'') type.Nat ≡ True
Should have proved:
hasty (λy. if y = ''m'' then Some ?t1.1 else if y = ''n'' then Some ?t1.3 else $0 y) (Var ''n'') type.Nat
So if you want to use the simplifier for this kind of type checking, you need a different subgoaler. I'm not enough of an expert to help you with that. You can find more documentation in the Isabelle/Isar reference manual, section 9.3.6.
Instead, I recomment that you write your own type inference method (e.g., using Eisbach) that applies the type inference rules and calls the simplifier as needed. This avoids the problems with the subgoaler.
It seems that for this kind of proof goals, which can be solved by repeatedly applying an inference rule, one should use the classical reasoner (auto) rather than the rewriter (simpl).
If I declare all typing rules as safe intro rules:
declare HtVar [intro!]
declare HtConst [intro!]
declare HtPlus [intro!]
declare HtAbs [intro!]
declare HtApp [intro!]
Then most of my lemma is proved by auto, which leaves open two lookup goals that can be solved by simp:
lemma eleven: "hasty $0 (App (App
(Abs ''n'' (Abs ''m'' (Plus (Var ''n'') (Var ''m''))))
(Const 7)) (Const 4)) Nat"
apply(auto)
apply(simp_all add: my_simps)
done
Also, given a classical reasoner such as auto, one can easily specify what rewriter to use on the remaining subgoals as follows, so the above proof can be condensed into one line:
apply(auto simp add: my_simps)
Whereas, given a rewriter such as simp, it seems to be a bit more involved to specify what method to use on the remaining subgoals.
expect to use the subgoal to run the list which defined by let? aa = [1,2]
and run rev_app on this aa and show the value as [2,1]
theory Scratch2
imports Datatype
begin
datatype 'a list = Nil ("[]")
| Cons 'a "'a list" (infixr "#" 65)
(* This is the append function: *)
primrec app :: "'a list => 'a list => 'a list" (infixr "#" 65)
where
"[] # ys = ys" |
"(x # xs) # ys = x # (xs # ys)"
primrec rev :: "'a list => 'a list" where
"rev [] = []" |
"rev (x # xs) = (rev xs) # (x # [])"
primrec itrev :: "'a list => 'a list => 'a list" where
"itrev [] ys = ys" |
"itrev (x#xs) ys = itrev xs (x#ys)"
value "rev (True # False # [])"
lemma app_Nil2 [simp]: "xs # [] = xs"
apply(induct_tac xs)
apply(auto)
done
lemma app_assoc [simp]: "(xs # ys) # zs = xs # (ys # zs)"
apply(induct_tac xs)
apply(auto)
done
(1 st trial)
lemma rev_app [simp]: "rev(xs # ys) = (rev ys) # (rev xs)"
apply(induct_tac xs)
thus ?aa by rev_app
show "rev_app [1; 2]"
(2nd trial)
value "rev_app [1,2]"
(3 rd trial)
fun ff :: "'a list ⇒ 'a list"
where "rev(xs # ys) = (rev ys) # (rev xs)"
value "ff [1,2]"
thus ?aa by rev_app
show "rev_app [1; 2]"
end
Firstly, you need the syntax for list enumeration (I just picked it up in the src/HOL/List.thy file):
syntax
-- {* list Enumeration *}
"_list" :: "args => 'a list" ("[(_)]")
translations
"[x, xs]" == "x#[xs]"
"[x]" == "x#[]"
Then, is one of the following what you're searching for ?
Proposition 1:
lemma example1: "rev [a, b] = [b, a]"
by simp
This lemma is proved by applying the definition rules of rev that are used by the method simp to rewrite the left-hand term and prove that the two sides of the equality are equal. This is the solution I prefer because you can see the example is satisfied even without evaluating it with Isabelle.
Proposition 2:
value "rev [a, b]" (* return "[b, a]" *)
Here and in Proposition 3, we just uses the command value to evaluate rev.
Proposition 3:
value "rev [a, b] = [b, a]" (* returns "True" *)
This lemma is not used by the previous propositions:
lemma rev_app [simp]: "rev(xs # ys) = (rev ys) # (rev xs)"
apply (induct_tac xs)
by simp_all
Notes:
As a general principle, you shouldn't import the "Datatype" package alone, but import "Main" instead.
In your 1st attempt you're mixing the "apply" (apply ...) and the "structured proof" (thus ...) styles
"thus ?aa" makes no sense if "?aa" is "[1,2]" as the argument of "thus" should be a subgoal, ie. a proposition with a boolean value.
To evaluate, the command "value" uses ML execution or if this fails, normalisation by evaluation.
In example1, you can use a custom proof and thus lemmas (for example: by (simp add:rev_app)