Defining functions between constants in Isabelle - isabelle

I'm a mathematician just starting to get used to Isabelle, and something that should be incredibly simple turned out to be frustrating. How do I define a function between two constants? Say, the function f: {1,2,3} \to {1,2,4} mapping 1 to 1, 2 to 4 and 3 to 2?
I suppose I managed to define the sets as constants t1 and t2 without incident, but (I guess since they're not datatypes) I can't try something like
definition f ::"t1 => t2" where
"f 1 = 1" |
"f 2 = 4" |
"f 3 = 2"
I believe there must be a fundamental misconception behind this difficulty, so I appreciate any guidance.

There's a number of aspects to your question.
First, to get something working quickly, use the fun keyword instead of definition, like so:
fun test :: "nat ⇒ nat" where
"test (Suc 0) = 1" |
"test (Suc (Suc 0)) = 4" |
"test (Suc (Suc (Suc 0))) = 2" |
"test _ = undefined"
You cannot pattern match on any arguments directly in the head of the definition using the definition keyword, whereas you can with fun. Note also that I have replaced the overloaded numeric literals (1, 2, 3, etc.) with the constructors for the nat datatype (0 and Suc) in the pattern match.
An alternative would be to stick with definition, but push the case analysis of the function's argument inside the body of the definition using a case statement, like so:
definition test2 :: "nat ⇒ nat" where
"test2 x ≡
case x of
(Suc 0) ⇒ 1
| (Suc (Suc 0)) ⇒ 4
| (Suc (Suc (Suc 0))) ⇒ 2
| _ ⇒ undefined"
Note that definitions like test2 are not unfolded by the simplifier by default, and you will need to manually add the theorem test2_def to the simplifier's simpset if you want to expand occurrences of test2 in a proof.
You can also define new types (you cannot use sets as types, directly, as you are trying to do) corresponding to your two three-element sets with typedef, but personally I would stick with nat.
EDIT: to use typedef, do something like:
typedef t1 = "{x::nat. x = 1 ∨ x = 2 ∨ x = 3}"
by auto
definition test :: "t1 ⇒ t1" where
"test x ≡
case (Rep_t1 x) of
| Suc 0 ⇒ Abs_t1 1
| Suc (Suc 0) ⇒ Abs_t1 4
| Suc (Suc (Suc 0)) ⇒ Abs_t1 2"
Though, I don't really ever use typedef myself, and so this may not be the best way of using this and others may possibly suggest some other way. What typedef does is carve out a new type from an existing one, by identifying a non-empty set of inhabitants for the new type. The proof obligation, here closed by auto, is merely to demonstrate that the defining set for the new type is indeed non-empty, and in this case I am carving out a three-element set of naturals into a new type, called t1, so the proof is fairly trivial. Two new constants are created, Abs_t1 and Rep_t1 which allow you to move back-and-forth between the naturals and the new type. If you put a print_theorems after the typedef command you will see several new theorems about t1 that Isabelle has automatically generated for you.

Related

What does "case _ of _" mean in Isabelle

While reading this answer on quotient types, I stumbled upon the construct "case _ of _ ⇒ _". Upon checking the manual, there's no such definition, but there are separate definitions for "case _" (§6.5.1) and "of _" (§6.4.3). Nonetheless, reading these definitions only confused me more about the meaning of this construct.
Consequently, I decided to come up with a simpler version of the lemma that I might be able to prove, which was this one:
lemma test: "(case n of (0::nat,0::nat) ⇒ (a,b) = n) ⟹ a = 0 ∧ b = 0"
In my head, after analyzing the context of the mentioned question, this statement should be equivalent to "(a,b) = (0,0) ⟹ a = 0 ∧ b = 0", which should be trivial to prove. Well, sledgehammer begs to differ:
"cvc4": Timed out
"z3": Timed out
"e": Timed out
"spass": Timed out
"remote_vampire": The prover gave up
So it seems like I'm misunderstanding what this construct is meant to represent.
In light of that, what is the meaning of the statement "case _ of _ ⇒ _" in Isabelle?
In Isabelle, a statement of the type case _ of _ ⇒ _ | _ ⇒ _ | _ ⇒ _ | ... is a form of pattern matching.
You might want to take a look at §2.5.4 of Isabelle's tutorial (§2.5.5 and §2.5.6 are also useful). This question on pattern matching and the Wikipedia article may provide more information about pattern matching in general.
What you are missing is that there not guarantee that the patterns are exhaustive. If no pattern matches, the result is undefined.
Nitpick actually finds a counter-example automatically on your lemma:
Nitpicking formula...
Kodkod warning: Interrupt
Nitpick found a counterexample:
Free variables:
a = 1
b = 0
n = (0, 1)
Let's plugin back the value of n:
lemma ‹(case (0,1) of (0::nat,0::nat) ⇒ (a,b) = n)⟹ a = 0 ∧ b = 0›
apply simp
(*
proof (prove)
goal (1 subgoal):
1. undefined ⟹ a = 0 ∧ b = 0
*)
EDIT, to answer the question below:
(case (0,1) of (0::nat,0::nat) ⇒ (a,b) = n) means roughly [1]:
(case_prod (0,1) of
(0::nat,0::nat) ⇒ (a,b) = n
| _ ⇒ undefined)
where case_prod is the destructor for pairs. Hence, if you don't match any of the patterns, the result is undefined.
[1] full output:
ML ‹#{term ‹(case (0,1) of (0::nat,0::nat) ⇒ (a,b) = n)›}›
As mentioned, nitpick is helpful here. Luckily the fix is simple.
lemma test: "(case n of (0::nat,0::nat) ⇒ (a,b) = n | _ ⇒ False) ⟹ a = 0 ∧ b = 0"
Because you don't bind any variables, rewriting your hypothesis into a conditional statement is trivial. Finally, you might want to look into the concept of pattern matching, specifically in the context of functional programming.
lemma test': "(case n of (0::nat,0::nat) ⇒ (a,b) = n | _ ⇒ (a,b) = (0,0)) = ((a,b) = (0,0))"
lemma test'': "(case n of (0::nat,0::nat) ⇒ (a,b) = n) = (if n = (0,0) then (a,b) = n else undefined)"

How can i cast nat to int in Isabelle

While doing my homework I got stuck in the following question:
Create the function add. It returns the sum of two natural numbers.
Prove that it works.
I'm stuck because to write the code to prove it, I need to cast a natural number (nat in Isabelle) to int.
Here's the add function that I wrote:
primrec add :: "nat ⇒ nat ⇒ nat"
where
add01: "add x 0 = x" |
add02: "add x (Suc y) = Suc (add x y) "
To know the result I did this:
value "somaNat (Suc(Suc(0))) (Suc(0))"
It returns 3, as it should.
Suc(Suc(0) = 2
Suc(0) = 1
Also tried to create a function that cast it to int, like this:
primrec nat_to_int :: "nat ⇒ int"
where
nat_to_int02: "nat_to_int x = value x"
It does not work because (value x) can't be on the right side.
I searched for it in the tutorial that Isabelle provides, and in another one, that I found online.
The closest question I found on SO was this one:
Casting int to nat in Isabelle
So, how can I cast nat to int in Isabelle?

Eisbach term parameters and subgoal_tac

I'm currently trying to get into Eisbach.
How can I achieve that in the subgoal_tac (see below) the value
of the parameter A is used, and that A is not interpreted as some variable name? Is there some general way to do this or would this need special tailoring of the subgoal_tac tactic?
theory Scratch (* Isabelle2019 *)
imports
Main
"HOL-Eisbach.Eisbach"
begin
method test for A :: nat =
subgoal_tac "A = 5"
lemma "True"
apply (test 1)
(*
proof (prove)
goal (2 subgoals):
1. A = 5 ⟹ True
2. A = 5
*)
(* The A has a yellow background in the output pane*)
oops
end
I don't know why it does not work with subgoal_tac. I think I read somewhere that all methods ending with _tac are kind of deprecated now.
As a workaround you could use a Lemma:
method test for A :: nat =
(rule meta_mp[where P="A = 5"])
lemma "True"
apply (test 1)
(*
goal (2 subgoals):
1. 1 = 5 ⟹ True
2. 1 = 5
*)

Proving properties of generated lists

My aim is to prove properties of lists containing generated patterns.
In the first example the pattern is simply a sequence of 0s and lemma pattern_0_len proves that the length of the generated list indeed equals to the length parameter of the generator function.
theory pattern_0
imports Main
begin
fun pattern_0 :: "nat ⇒ nat list" where
"pattern_0 0 = []" |
"pattern_0 len = (pattern_0 (len - 1)) # [0]"
lemma pattern_0_len [simp]: "length (pattern_0 lng) = lng"
apply(induction lng)
apply(simp)
apply(auto)
done
end
In the second example the generator produces a sequence of 0, 1 items.
theory pattern_0_1
imports Main
begin
fun pattern_0_1 :: "nat ⇒ nat ⇒ nat list" where
"pattern_0_1 0 item = []" |
"pattern_0_1 len item = (pattern_0_1 (len - 1) (if item = 0 then 1 else 0)) # [item]"
lemma pattern_0_1_len [simp]: "length (pattern_0_1 lng item) = lng"
apply(induction lng)
apply(simp)
apply(auto)
done
end
Unfortunately, pattern_0_1_len does not prove (after simp the goal is exactly the induction step) and I'd like to understand the reason why not. Is it the presence of the item parameter that 'confuses' Isabelle? What can be done in this situation, preferably without declaring anything about how the pattern is generated?
The additional parameter is indeed the problem. For example, consider this subgoal:
1. ⋀lng. length (pattern_0_1 lng 0) = lng ⟹ item = 0 ⟹ length (pattern_0_1 lng (Suc 0)) = lng
You see that the induction hypothesis is only applicable for zero, but you need it for one.
The fix is simple:
apply(induction lng arbitrary: item)
This instructs the induction method to first generalize the variable item. Then, the induction hypothesis becomes more broadly applicable.

How to generate code for reverse sorting

What is the easiest way to generate code for a sorting algorithm that sorts its argument in reverse order, while building on top of the existing List.sort?
I came up with two solutions that are shown below in my answer. But both of them are not really satisfactory.
Any other ideas how this could be done?
I came up with two possible solutions. But both have (severe) drawbacks. (I would have liked to obtain the result almost automatically.)
Introduce a Haskell-style newtype. E.g., if we wanted to sort lists of nats, something like
datatype 'a new = New (old : 'a)
instantiation new :: (linorder) linorder
begin
definition "less_eq_new x y ⟷ old x ≥ old y"
definition "less_new x y ⟷ old x > old y"
instance by (default, case_tac [!] x) (auto simp: less_eq_new_def less_new_def)
end
At this point
value [code] "sort_key New [0::nat, 1, 0, 0, 1, 2]"
yields the desired reverse sorting. While this is comparatively easy, it is not as automatic as I would like the solution to be and in addition has a small runtime overhead (since Isabelle doesn't have Haskell's newtype).
Via a locale for the dual of a linear order. First we more or less copy the existing code for insertion sort (but instead of relying on a type class, we make the parameter that represents the comparison explicit).
fun insort_by_key :: "('b ⇒ 'b ⇒ bool) ⇒ ('a ⇒ 'b) ⇒ 'a ⇒ 'a list ⇒ 'a list"
where
"insort_by_key P f x [] = [x]"
| "insort_by_key P f x (y # ys) =
(if P (f x) (f y) then x # y # ys else y # insort_by_key P f x ys)"
definition "revsort_key f xs = foldr (insort_by_key (op ≥) f) xs []"
at this point we have code for revsort_key.
value [code] "revsort_key id [0::nat, 1, 0, 0, 1, 2]"
but we also want all the nice results that have already been proved in the linorder locale (that derives from the linorder class). To this end, we introduce the dual of a linear order and use a "mixin" (not sure if I'm using the correct naming here) to replace all occurrences of linorder.sort_key (which does not allow for code generation) by our new "code constant" revsort_key.
interpretation dual_linorder!: linorder "op ≥ :: 'a::linorder ⇒ 'a ⇒ bool" "op >"
where
"linorder.sort_key (op ≥ :: 'a ⇒ 'a ⇒ bool) f xs = revsort_key f xs"
proof -
show "class.linorder (op ≥ :: 'a ⇒ 'a ⇒ bool) (op >)" by (rule dual_linorder)
then interpret rev_order: linorder "op ≥ :: 'a ⇒ 'a ⇒ bool" "op >" .
have "rev_order.insort_key f = insort_by_key (op ≥) f"
by (intro ext) (induct_tac xa; simp)
then show "rev_order.sort_key f xs = revsort_key f xs"
by (simp add: rev_order.sort_key_def revsort_key_def)
qed
While with this solution we do not have any runtime penalty, it is far too verbose for my taste and is not easily adaptable to changes in the standard code setup (e.g., if we wanted to use the mergesort implementation from the Archive of Formal Proofs for all of our sorting operations).

Resources