How to define a data type with constraints? - isabelle

For example I need to define a data type for pairs of list, both of which must have the same length:
type_synonym list2 = "nat list × nat list"
definition good_list :: "list2" where
"good_list ≡ ([1,2],[3,4])"
definition bad_list :: "list2" where
"bad_list ≡ ([1,2],[3,4,5])"
I can define a separate predicate, which checks whether a pair of lists is ok:
definition list2_is_good :: "list2 ⇒ bool" where
"list2_is_good x ≡ length (fst x) = length (snd x)"
value "list2_is_good good_list"
value "list2_is_good bad_list"
Is it possible to combine the datatype and the predicate? I've tried to use inductive_set, but I have no idea how to use it:
inductive_set ind_list2 :: "(nat list × nat list) set" where
"length (fst x) = length (snd x) ⟹
x ∈ ind_list2"

You can create a new type which is constraint by some predicate via typedef, though the result will just be a type and not a datatype.
typedef good_lists2 = "{xy :: list2. list2_is_good xy}"
by (intro exI[of _ "([],[])"], auto simp: list2_is_good_def)
Working with such a newly created type is best done via the lifting-package.
setup_lifting type_definition_good_lists2
Now for every operation on this new lifted type good_lists2,
you first have
to lift the operation from the raw type list2.
For instance, below we define an extraction function and a Cons-function.
In the latter you have prove that indeed the newly generated pair satisfies the invariant.
lift_definition get_lists :: "good_lists2 ⇒ list2" is "λ x. x" .
lift_definition Cons_good_lists2 :: "nat ⇒ nat ⇒ good_lists2 ⇒ good_lists2"
is "λ x y (xs,ys). (x # xs, y # ys)"
by (auto simp: list2_is_good_def)
Of course, you it is also possible to access the invariant
of the lifted type.
lemma get_lists: "get_lists xy = (x,y) ⟹ length x = length y"
by (transfer, auto simp: list2_is_good_def)
I hope this helps.

René's answer is the answer to what you asked for, but just for the sake of completeness, I would like to add two things:
First, stating the obvious here: It seems like it would be much easier if you just worked with lists of pairs instead of pairs of lists. Your proposed new type is clearly isomorphic to a list of pairs. Then you don't have to introduce an extra type.
Also, on a more general note, just because you can introduce new types with type definitions in Isabelle that capture certain invariants does not mean that this is always the best idea. It may be easier to just carry around the invariants separately. It depends very much on what those invariants look like and what you actually do with the values of that type. In many cases, I would argue that the additional boilerplate for setting up the new type (in particular class instantiations if you need those) and converting between the base type and the new type is not worth whatever abstraction benefit you get from it.
A good heuristic, I think, is to ask yourself whether the type you are introducing is more of a ‘throw-away’ thing that you need in one specific place – then don't introduce a new type for it – or whether it is something that you can prove nice general facts about and introduce a good abstract theory on – then do introduce a new type for it. Good examples from the distribution for the latter are things like multisets, finite sets, and probability mass functinos.

Related

What is `ind` type in Isabelle

In Isabelle natural numbers are defined as follows
typedecl ind
axiomatization Zero_Rep :: ind and Suc_Rep :: "ind ⇒ ind"
― ‹The axiom of infinity in 2 parts:›
where Suc_Rep_inject: "Suc_Rep x = Suc_Rep y ⟹ x = y"
and Suc_Rep_not_Zero_Rep: "Suc_Rep x ≠ Zero_Rep"
subsection ‹Type nat›
text ‹Type definition›
inductive Nat :: "ind ⇒ bool"
where
Zero_RepI: "Nat Zero_Rep"
| Suc_RepI: "Nat i ⟹ Nat (Suc_Rep i)"
That's a lot of code to write what's effectively just
datatype nat = Zero | Suc nat
Is there some greater purpose to ind or maybe it is there just for historical reasons?
The datatype package needs a whole lot of maths to do all those internal constructions that are required to give you the datatype you want in the end. In particular, it needs natural numbers.
So the reason why the datatype package is not used to define the naturals is that it simply isn't available yet at that point.
One could of course just axiomatise the nat type directly. I think the idea to instead axiomatise some infinite type and then carve the naturals out of that is a standard one, I think it is done similarly in Zermelo–Fraenkel logic.
Side note: In fact one could even make the datatype package itself axiomatic. But the common philosophy in interactive theorem provers, especially in the LCF family, is to work from a small set of axioms and that everything that can be constructed should be constructed instead of axiomatised. This reduces the amount of "trusted code".
A direct axiomatisation of the natural numbers would not be very controversial I think, but for something as complicated as the datatype package an axiomatic implementation would h introduce lots of possibilities for subtle soundness bugs.

Defining a predicate; need prop=>bool

I'm trying to define a function that takes a set and a relation and returns a bool telling if the relation is reflexive on the set. I tried to define it like this:
definition refl::"'a set⇒('a×'a) set⇒bool" where
"refl A R = (∀x. x∈A⟹(x,x)∈R)"
but Isabelle gives me the following error:
Type unification failed: Clash of types "prop" and "bool"
Type error in application: incompatible operand type
Operator: (=) (refl A R) :: bool ⇒ bool
Operand: ∀x. x ∈ A ⟹ (x, x) ∈ R :: prop
I can't seem to find any function to force a "prop" into a "bool". I also tried changing the definition to set the RHS = True, but I get the same error.
What is the correct way to define my function?
You can't go from prop to bool. But you don't have to: just use the object level connectives (⟶ and ∀) instead of the meta-logical ones (⟹ and ⋀). They are logically equivalent, so this is not a problem.
The meta-logical connectives should (and usually can) only be used on the ‘outermost level’ of a proposition.
Note however that when you can use the mega-logical ones, it is usually more convenient to use them because the object-level ones are opaque to Isabelle and the Isar proof language (i.e. they are functions just like any other function) whereas Isar ‘knows’ what ⟹ and ⋀ mean. For instance, if you have a fact stated with ⟹ and ⋀, you can immediately instantiate variables and discharge assumptions in it using the of/OF attributes.
You need to write it such that the value is not prop in the first place — there's no conversion. In this case, you used the prop-level implication ⟹ between ∀x. x ∈ A and (x, x) ∈ R. You may use a single-width arrow --> instead, which is an implication of bools.

Reasoning about the entirety of a codatatype in Isabelle/HOL

I'd like to write down some definitions (and prove some lemmas!) about paths in a graph. Let's say that the graph is given implicitly by a relation of type 'a => 'a => bool. To talk about a possibly infinite path in the graph, I thought a sensible thing was to use a lazy list codatatype like 'a llist as given in "Defining (Co)datatypes and Primitively (Co)recursive Functions in Isabelle/HOL" (datatypes.pdf in the Isabelle distribution).
This works well enough, but I'd then I'd like to define a predicate that takes such a list and a graph relation and evaluates to true iff the list defines a valid path in the graph: any pair of adjacent entries in the list must be an edge.
If I was using 'a list as a type to represent the paths, this would be easy: I'd just define the predicate using primrec. However, the co-inductive definitions I can find all seem to generate or consume the data one element at a time, rather than being able to make a statement about the whole thing. Obviously, I realise that the resulting predicate won't be computable (because it is making a statement about infinite streams), so it might have a \forall in there somewhere, but that's fine - I want to use it for developing a theory, not generating code.
How can I define such a predicate? (And how could I prove the obvious associated introduction and elimination lemmas that make it useful?)
Thanks!
I suppose the most idiomatic way to do this is to use a coinductive predicate. Intuitively, this is like a normal inductive predicate except that you also allow ‘infinite derivation trees’:
type_synonym 'a graph = "'a ⇒ 'a ⇒ bool"
codatatype 'a llist = LNil | LCons 'a "'a llist"
coinductive is_path :: "'a graph ⇒ 'a llist ⇒ bool" for g :: "'a graph" where
is_path_LNil:
"is_path g LNil"
| is_path_singleton:
"is_path g (LCons x LNil)"
| is_path_LCons:
"g x y ⟹ is_path g (LCons y path) ⟹ is_path g (LCons x (LCons y path))"
This gives you introduction rules is_path.intros and an elimination rule is_path.cases.
When you want to show that an inductive predicate holds, you just use its introduction rules; when you want to show that an inductive predicate implies something else, you use induction with its induction rule.
With coinductive predicates, it is typically the other way round: When you want to show that a coinductive predicate implies something else, you just use its elimination rules. When you want to show that a coinductive predicate holds, you have to use coinduction.

Using the ordering locale with partial maps

The following code doesn't typecheck:
type_synonym env = "char list ⇀ val"
interpretation map: order "op ⊆⇩m :: (env ⇒ env ⇒ bool)" "(λa b. a ≠ b ∧ a ⊆⇩m b)"
by unfold_locales (auto intro: map_le_trans simp: map_le_antisym)
lemma
assumes "mono (f :: env ⇒ env)"
shows "True"
by simp
Isabelle complains with the following error at the lemma:
Type unification failed: No type arity option :: order
Type error in application: incompatible operand type
Operator: mono :: (??'a ⇒ ??'b) ⇒ bool
Operand: f :: (char list ⇒ val option) ⇒ char list ⇒ val option
Why so? Did I miss something to use the interpretation? I suspect I need something like a newtype wrapper here...
When you interpret a locale like order which corresponds to a type class, you only get the theorems proved inside the context of the locale. However, the constant mono is only defined on the type class. The reason is that mono's type contains two type variables, whereas only one is available inside locales from type classes. You can notice this because there is no map.mono stemming from your interpretation.
If you instantiate the type class order for the option type with None being less than Some x, then you can use mono for maps, because the function space instantiates order with the pointwise order. However, the ordering <= on maps will only be semantically equivalent to ⊆⇩m, not syntactically, so none of the existing theorems about ⊆⇩m will work for <= and vice versa. Moreover, your theories will be incompatible with other people's that instantiate order for option differently.
Therefore, I recommend to go without type classes. The predicate monotone explicitly takes the order to be used. This is a bit more writing, but in the end, you are more flexible than with type classes. For example, you can write monotone (op ⊆⇩m) (op ⊆⇩m) f to express that f is a monotone transformation of environments.

What is a Quotient type pattern in Isabelle?

What is a "Quotient type pattern" in Isabelle?
I couldn't find any explanation over the internet.
It would be better if you would quote a little from where you saw the phrase. I know of "pattern matching," and I know of "quotient type," but I don't know of "quotient type pattern."
I prefer not to ask for clarification, and then wait, so I pick two of the three words, "quotient type." If I'm on the wrong track, it's still a worthy subject, and a big and important part of Isabelle/HOL.
There is the quotient_type keyword, and it allows you to define a new type with an equivalence relation.
It is part of the quotient package, described starting on page 248 of isar-ref.pdf. There happens to be a Wiki page, Quotient_type.
A more involved description is given by Brian Hufmann and Ondřej Kunčar. Go to Kunčar's web page and look at the two PDFs titled Lifting and Transfer: A Modular Design for Quotients in Isabelle/HOL, which are not exactly the same.
It happens to be that lifting and quotient types are heavily related, and not easy to understand, which is why I try to study a little here and there, like right now, to get a better understanding of it all.
Integers and Rationals in HOL Are Quotient Types, I Pick One as an Example, Integers
You can start by looking Int.thy.
For a quotient type, you need an equivalence relation, which defines a set, and intrel is what is used to define that set for type int.
definition intrel :: "(nat * nat) => (nat * nat) => bool" where
"intrel = (%(x, y) (u, v). x + v = u + y)"
This is the classic definition of the integers, based on the natural numbers. Integers are ordered pairs of natural numbers (and sets as I describe below), and they're equal by that definition.
For example, informally, (2,3) = (4,5) because 2 + 5 = 4 + 3.
I'm boring you, and you're waiting for the good stuff. Here's part of it, the use of quotient_type:
quotient_type int = "nat * nat" / "intrel"
morphisms Rep_Integ Abs_Integ
Those two morphisms come into play, if you want to strain your brain, and really understand what's going on, which I do. There are lots of functions and simp rules that quotient_type generates, and you have to do a lot of work to find it all, such as with the find_theorems command.
An Abs function abstracts an ordered pair to an int. Check these out:
lemma "Abs_Integ(1,0) = (1::int)"
by(metis one_int_def)
lemma "Abs_Integ(x,0) + Abs_Integ(y,0) ≥ (0::int)"
by(smt int_def)
They show that an int really is an ordered pair, under the hood of the engine.
Now I show the explicit types of those morphisms, along with Abs_int and Rep_int, which show int not only as an ordered pair, but as a set of ordered pairs.
term "Abs_int :: (nat * nat) set => int"
term "Abs_Integ :: (nat * nat) => int"
term "Rep_int :: int => (nat * nat) set"
term "Rep_Integ :: int => (nat * nat)"
I'm boring you again, but I have an emotional need to show some more examples. Two positive integers are equal if the components of the ordered pairs differ by one, such as these:
lemma "Abs_Integ(1,0) = Abs_Integ(3,2)"
by(smt nat.abs_eq split_conv)
lemma "Abs_Integ(4,3) = Abs_Integ(3,2)"
by(smt nat.abs_eq split_conv)
What would you expect if you added Abs_Integ(4,3) and Abs_Integ(3,2)? This:
lemma "Abs_Integ(2,3) + Abs_Integ(3,4) = Abs_Integ(2 + 3, 3 + 4)"
by(metis plus_int.abs_eq plus_int_def split_conv)
That plus_int in the proof is defined in Int.thy, on line 44.
lift_definition plus_int :: "int => int => int"
is "%(x, y) (u, v). (x + u, y + v)"
What is this lifting all about? That would put me at "days into" this explanation, and I'm only just starting to understand it a little.
The find_theorems shows there's lots of stuff hidden, as I said:
thm "plus_int.abs_eq"
find_theorems name: "Int.plus_int*"
More examples, but these are to emphasize that, under the hood of the engine, an int ties back into an equivalence class as a set, where I'm using intrel above to define the sets right:
term "Abs_int::(nat * nat) set => int"
term "Abs_int {(x,y). x + 3 = 2 + y}" (*(2,3)*)
term "Abs_int {(x,y). x + 4 = 3 + y}" (*(3,4)*)
lemma "Abs_int {(x,y). x + 3 = 2 + y} = Abs_int {(x,y). x + 100 = 99 + y}"
by(auto)
That auto proof was easy, but there's no magic coming through for me on this next one, even though it's simple.
lemma "Abs_int {(x,y). x + 3 = 2 + y} + Abs_int {(x,y). x + 4 = 3 + y}
= Abs_int {(x,y). x + 7 = 5 + y}"
apply(auto simp add: plus_int.abs_eq plus_int_def intrel_def)
oops
It could be that all I need to do is tap into something that's not a simp rule by default.
If quotient_type is not the "quotient type pattern" you're talking about, at least I got something out of it by seeing all what find_theorems returns about Int.plus_int* above.
What is a quotient type?
A quotient type is a way to define a new type in terms of an already existing type. That way, we don't have to axiomatize the new type. For example, one might find reasonable to use the naturals to build the integers, since they can be seen as "naturals+negatives". You may then want to use the integers to build the rationals, since they can be seen as "integers+quotients". And so on.
Quotient types use a given equivalence relation on the "lower type" to determine what equality means for the "higher type".
Being more precise: A quotient type is an abstract type for which equality is dictated by some equivalence relation on its underlying representation.
This definition might be too abstract at first, so we'll use the integers as a grounding example.
Example: Integers from Naturals
If one wants to define the integers, the most standard way is to use an ordered pair of natural numbers, such as (a,b), which intuitively represents "a-b". For example, the number represented by the pair (2,4) is -2, since intuitively 2-4 = -2. By the same logic, (0,2) also represents '-2', and so does (1,3) or (10,12), since 0-2 = 1-3 = 10-12 = -2.
We could then say that "two pairs (a,b) and (x,y) represent the same integer iff a - b = x - y". However, the minus operation can be weird in natural numbers (what is '2-3' in the naturals?). To avoid that weirdness, rewrite 'a - b = x - y' as 'a + y = x + b', now using only addition. So, two pairs (a,b) and (x,y) represent the same integer when 'a + y = x + b'. For example, (7,9) represents the same integer as (1,3), since '7 + 3 = 1 + 9'.
That leads to a quotient definition of integers: An integer is a type represented by an ordered pair of natural numbers. Two integers represented by (a,b) and (x,y) are equal if, and only if, a+y = x+b.
The integer type derives from the type "ordeded pair of natural numbers" which is its representation. We may call the integer itself an abstraction of that. The equality of integers is defined as whenever some underlying representations '(a,b)' and '(x,y)' follow the equivalence relation 'a+y = x+b'.
In that sense, the integer '-3' is represented by both '(0,3)' and '(2,5)', and we may show this by noticing that 0+5 = 3+2. On the other hand, '(0,3)' and '(6,10)' do not represent the same integer, since '0+10 ≠ 3+6'. This reflects the fact that '-3 ≠ -4'.
Technically speaking, the integer '-3' is not specifically '(0,3)', nor '(1,4)', nor '(10,13)', but the whole equivalence class. By that I mean that '-3' is the set containing all of its representations (i.e. -3 = { (0,3), (1,4), (2,5), (3,6), (4,7), ... }). '(0,3)' is called a representation for '-3', and '-3' is the abstraction of '(0,3)'.
Morphisms: Rep and Abs in Isabelle
Rep and Abs are ways for us to transition between the representations and the abstractions they represent. More precisely, they are mappings from an equivalence class to one of its representations, and vice-versa. We call them morphisms.
Rep takes an abstract object (an equivalence class), such as '-3', and transforms it into one of its representations, for example '(0,3)'. Abs does the opposite, taking a representation such as '(3,10)', and mapping it into its abstract object, which is '-7'. Int.thy (Isabelle's implementation of integers) defines these as Rep_Integ and Abs_Integ for integers.
Notice that the statement '(2,3) = (8,9)' is an absurd. Since these are ordered pairs, that would imply '2 = 8' and '3 = 9'. On the other hand the statement 'Abs_Integ(2,3) = Abs_Integ(8,9)' is very much true, as we are simply saying that the integer abstraction of '(2,3)' is the same as the integer abstraction '(8,9)', namely '-1'.
A more precise phrasing of 'Abs_Integ(2,3) = Abs_Integ(8,9)' is: "'(2,3)' and '(8,9)' belong in the same equivalence class under the integer relation". We usually call this class '-1'.
It's important to note that '-1' is just a convenient shorthand for "the equivalence class of (0,1)", in the same vein that '5' is just a shorthand for "the equivalence class of (5,0)" and '-15' is shorthand for "the equivalence class of '(0,15)'. We call '(0,1)', '(5,0)', and '(0,15) the canonical representations. So saying "Abs_Integ(2,3) = -1" is really just a nice abbreviation for "Abs_Integ(2,3) = Abs_Integ(0,1)" .
It's also worth noting that the mapping Rep is one-to-one. This means that Rep_Integ(-1) will always yield the same representation pair, usually the canonical '(0,1)'. The specific pair picked does not matter much, but it'll always pick the same one. That is useful to know, as it implies that the statement Rep_Integ(i) = Rep_Integ(i) is always true.
The quotient_type command in Isabelle
'quotient_type' creates a quotient type using the specified type and equivalence relation. So quotient_type int = "nat × nat" / "intrel" creates the quotient type int, as the equivalence classes of nat × nat under the relation intrel (where "intrel = (λ(a,b) (x,y). a+y = x+b)"). Section 11.9.1 of the manual details the specifics about the command.
It's worth noting that you actually have to prove that the relation provided (intrel) is an equivalence.
Here's a usage example from Int.thy, which defines the integers, it's morphisms, and proves that intrel is an equivalence relation:
(* Definition *)
quotient_type int = "nat × nat" / "intrel"
morphisms Rep_Integ Abs_Integ
(* Proof that 'intrel' is indeed an equivalence *)
proof (rule equivpI)
show "reflp intrel" by (auto simp: reflp_def)
show "symp intrel" by (auto simp: symp_def)
show "transp intrel" by (auto simp: transp_def)
qed
Definitions and Lemmas: The Lifting and Transfer packages
Now, the previous explanations suggest that Rep and Abs should appear everywhere, right? These transformations are crucial for proving properties about quotient types. However, they appear less than 10 times throughout the 2000 lines of Int.thy. Why?
lift_definition and the proof method transfer are the answer. They come from the Lifting and Transfer packages. These packages do a lot, but for our purposes, they do the job of concealing Rep and Abs from your definitions and theorems.
The gist when working with quotient types in Isabelle, is that you want to [1] define some operations, [2] prove some useful lemmas with the representation type, and then [3] completely forget about these representations, working only with the abstract type. When proving theorems about the abstract type, you should be using the previously shown properties and lemmas.
To get [1], lift_definition helps you to define the operations. In specific, it allows you to define a function with the representation type, and it automatically "lifts" it to the abstract type.
As an example, you can define addition on integers as such:
lift_definition int_plus:: "int ⇒ int ⇒ int"
is "λ(a,b)(c,d). (a+c, b+d)"
This definition is stated in terms of nat × nat ⇒ nat × nat ⇒ nat × nat, but 'lift_definition' will automatically "lift" it to int ⇒ int ⇒ int.
An important thing to note is that you have to prove the function still follows the equivalence relation after applied (i.e. if 'x ≃ y' then 'f x ≃ f y'). The definition above for example, will prompt you to prove that "if '(a,b) ≃ (x,y)' and '(c,d) ≃ (u,v)', then '(a+c,b+d) ≃ (x+u,y+v)'" (if it doesn't look like it, try using apply clarify).
One of the nice things about lift_definition is that it works in terms of the underlying representation only, so you don't have to worry about transitioning between abstractions and representations. Hence the lack of Rep_Integ and Abs_Integ in Int.thy.
It also sets up a transfer rule for the function. This how you get [2]: proving properties without having to worry about Rep and Abs. Using the transfer proof method, you can bring a lemma about an abstraction down to the representation level, and prove the desired property there.
As an example, you can state the commutativity of addition in the form int_plus x y = int_plus y x, and then use the transfer method to bring that statement down to the representation level, which after a clarify looks like intrel (a + c, b + d) (c + a, d + b). We can then prove by simplification with the definition of intrel:
lemma plus_comm: "int_plus x y = int_plus y x"
apply transfer
apply clarify
by (simp add: intrel_def)
And to get [3], you simply use these lemmas and properties of the abstract type, without worrying about the actual representations.
After this point, you'll even forget that you're using a quotient type, since the abstract type and it's properties are all you need. Usually a handful of lemmas on the abstract type is enough, and Int.thy will give you a lot more than a handful.
References and further reading
Section 1 of the paper "Quotient Types" gives a good overview of the topic (and goes in depth in the other sections).
The introduction of "Quotients Revisited for Isabelle/HOL" also explains very well the purpose of 'Rep' and 'Abs'.
"Lifting and Transfer" is also a great read into how these can be concealed and the automation behind quotient types in Isabelle.
Isabelle's Reference Manual (with some ctrl+f) is also a great source when in doubt about what specific commands do.

Resources