What does `class` do in Isabelle - isabelle

I'm trying to understand what
class gcd = zero + one + dvd +
fixes gcd :: "'a ⇒ 'a ⇒ 'a"
and lcm :: "'a ⇒ 'a ⇒ 'a"
begin
from GCD.thy means. Browsing through Nipkow's Programming and Proving didn't reveal any example, that explains what class does, not did the Tutorial on Isabelle/HOL.
Can you please explain to me what exactly class does ? I assume it defines a datatype.
Also, that fact that nothing follows after the last + seems irritating.

This is not a datatype, but a Haskell-style type class. Isabelle's type class system is described in Florian Haftmann's manual. The concept of locales is also closely related. Both are, I would say, advanced concepts in Isabelle that I would not recommend for a beginner to delve into. (which is also why the tutorials don't mention them)
To give you a brief overview: A type class is a way to talk about a collection of types that have something in common; for instance, the ring class talks about all types that form a ring: they have a 0 element, an addition and multiplication operation, and all of these fulfil certain laws. The gcd class you found is a type class for types that have a GCD and LCM defined on them (although it does not yet demand that these fulfil any laws; this is done in the semiring_gcd class.)
There is something after the final +: the fixes. The class declaration means that the class gcd is the intersection of the classes zero (type contains an element 0), the class one (contains a 1) and the class dvd (contains a notion of divisibility), and it additionally requires the presence of two functions gcd and lcm.
This class is a bit odd because it is purely syntactic, i.e. it does not require any laws to hold. Let's therefore also look at semiring_gcd:
class semiring_gcd = normalization_semidom + gcd +
assumes gcd_dvd1 [iff]: "gcd a b dvd a"
and gcd_dvd2 [iff]: "gcd a b dvd b"
and gcd_greatest: "c dvd a ⟹ c dvd b ⟹ c dvd gcd a b"
and normalize_gcd [simp]: "normalize (gcd a b) = gcd a b"
and lcm_gcd: "lcm a b = normalize (a * b) div gcd a b"
This class requires the presence of a gcd/lcm (by being based on the gcd class) and the type has to be a normalization_semidom (see below). Then there are a number of assumptions that also need to be satisfied.
So, to summarise, type classes are a nice way of organising common properties and operation of types, but I'd say that for beginners, it's not really important to understand every detail of them in order to be able to use them.
Bonus content: (not terribly relevant, just for the curious)
If you're wondering about the normalization_semidom and normalize: This is a very technical thing. Basically, if you have several associated elements (like 1 and -1, or, with polynomials ℝ[X], X and 2X), you typically have some notion of which of these is the ‘canonical’ representative: if I asked you what the GCD of 2 and 3 is, you would probably say 1 and not -1, and if I asked what the GCD of 2X and X² ∈ ℝ[X] is, you would probably say X and not 2X.

Related

How to define a function/map from one set to another (f: A -> B) in Isabelle?

What is the correct way in Isabelle/HOL (2021) to define a function f from a specific set A to another set B?
From mathematics, a function f: A -> B is often defined as a map from its domain A to its co-domain B. And a function f is defined as a special kind of relation in A × B with only one y ∈ B that satisfies x f y for each x ∈ A.
But in Isabelle/HOL, functions seems to be defined in terms of computation, e.g. f x = Suc x. It seems that there is no place to define domain and co-domains explicitly.
I was just wondering if there is a conventional way to define functions in Isabelle to be with domain and co-domains and be compatible with the definition of relations above.
Background
As you have noted, in Isabelle/HOL, conventionally, a function is a term of the type 'a⇒'b, where 'a and 'b can be arbitrary types. As such, all functions in Isabelle are total. There is a blog post by Joachim Breitner that explains this very well: link. I will not restate any elements of the content of the blog post: instead, I will concentrate on the issue that you have raised in your question.
Conventional definitions of a function
I am aware of two methodologies for the definition of a function in traditional mathematics (here I use the term "traditional mathematics" to mean mathematics exposed in some set-theoretic foundation):
According, for example, to [1,Chapter 6], a function is simply a single-valued binary relation.
Some authors [2,Chapter 2] identify a function with its domain and codomain by definition, i.e. a function becomes a triple (A,B,r), where r⊆A×B is still a single-valued binary relation and A is exactly the domain of the relation.
You can find some further discussion here. If the function is a binary relation, then the domain and the range are normally identified with the domain and the range of the relation that the function represents. However, it makes little sense to speak about the codomain of such an entity. If the function is defined as a triple (A,B,r), then the codomain is the assigned set B.
Isabelle/HOL I: functions as relations
Isabelle/HOL already provides a definition of the concept of a single-valued relation in the theory Relation.thy. The definition is implicit in the definition of the predicate single_valued:
definition single_valued :: "('a × 'b) set ⇒ bool"
where "single_valued r ⟷ (∀x y. (x, y) ∈ r ⟶ (∀z. (x, z) ∈ r ⟶ y = z))"
Thus, effectively, a single-valued relation is a term of the type ('a × 'b) set such that it satisfies the predicate single_valued. Some elementary results about this definition are also provided.
Of course, this predicate can be used to create a new type constructor of "functions-as-relations" from 'a to 'b. See the official documentation of Isabelle [3, section 11.7] and the article Lifting and Transfer: A Modular Design for Quotients in Isabelle/HOL [4, section 3] for further information about defining new type constructors in Isabelle/HOL. It is not unlikely that such a type is already available somewhere, but I could not find it (or anything similar) after a quick search of the sources.
Of course, there is little that can prevent one from providing a type that captures either of the set-theoretic definitions of a function presented in the previous subsection of the answer. I guess, something like the following definition could work, but I have not tested it:
typedef ('a, 'b) relfun =
‹
{
(A::'a set, B::'b set, f::('a × 'b) set).
single_valued f ∧ Domain f = A ∧ Range f ⊆ B
}
›
proof-
let ?r = ‹({}, {}, {})›
show ?thesis unfolding single_valued_def by (intro exI[of _ ?r]) simp
qed
Isabelle/HOL II: FuncSet and other restrictions
While the functions in Isabelle/HOL are total, one can still mimick the restriction of a function to a certain pre-defined domain (i.e. a proper subset of UNIV::'a set) using a variety of methodologies. One common methodology (exposed in the theory HOL-Library.FuncSet) is to force the function to be undefined on parts of the domain. My answer in the following thread explains this in more detail.
Isabelle/HOL III: HOL/ZF, ZFC in HOL and HOTG
This might be marginally off-topic. However, there exist extensions of Isabelle/HOL with the axioms of set-theory of different strengths [5,6,7]. For example, ZFC in HOL [6] provides a certain type V that represents the von Neumann universe. One can now define all relevant set-theoretic concepts internalized in this type, including, of course, either one of the conventional definitions of a function. In ZFC in HOL one can internalize functions defined in HOL using the so-called operator VLambda like so: (F::V) = VLambda (A::V) (f::V⇒V). Now, F is a single-valued binary relation internalized in the type V with the domain A and the values of the form ⟨x, f x⟩.
As a side note, I have exposed both definitions of a function as predicates on V explicitly while working on my own formalization of category theory: Category Theory for ZFC in HOL.
Summary
What is the correct way in Isabelle/HOL (2021) to define a function f
from a specific set A to another set B?
To answer your question directly, my opinion is that there is no single "correct way" to define a function from a specific set A to another set B. However, you have many options that you can explore: each of these options will have advantages and disadvantages that are specific to it.
References
Takeuti G, Zaring WM. Introduction to Axiomatic Set Theory. Heidelberg: Springer-Verlag; 1971.
Goldblatt R. Topoi: The Categorial Analysis of Logic. Mineola: Dover Publications; 2013.
Wenzel M. The Isabelle/Isar Reference Manual. 2019.
Huffman B, Kunčar O. Lifting and Transfer: A Modular Design for Quotients in Isabelle/HOL. In: Gonthier G, Norrish M, editors. Certified Programs and Proofs. Heidelberg: Springer; 2013. p. 131–46.
Obua S. Partizan Games in Isabelle/HOLZF. In: Barkaoui K, Cavalcanti A, Cerone A, editors. Theoretical Aspects of Computing - ICTAC 2006. Berlin: Springer; 2006. p. 272–86.
Paulson LC. Zermelo Fraenkel Set Theory in Higher-Order Logic. Archive of Formal Proofs. 2019.
Chen J, Kappelmann K, Krauss A. https://bitbucket.org/cezaryka/tyset/src [Internet]. HOTG. Available from: https://bitbucket.org/cezaryka/tyset/src.

How to prove the existence of inverse functions in Isabelle/HOL?

I am trying to prove the following basic theorem about the existence of the inverse function of a bijective function (to learn theorem-proving with Isabelle/HOL):
For any set S and its identity map 1_S, α:S→T is bijective iff there
exists a map β: T→S such that βα=1_S and αβ=1_S.
Below is what I have so far after some attempts to define relevant things including functions and their inverses. But I am pretty stuck and couldn't make much progress due to my lack of understanding of Isabelle and/or Isar.
theory Test
imports Main
"HOL.Relation"
begin
lemma bij_iff_ex_identity : "bij_betw f A B ⟷ (∃ g. g∘f = restrict id B ∧ f∘g = restrict id A)"
unfolding bij_betw_def inj_on_def restrict_def iffI
proof
let ?g = "restrict (λ y. (if f x = y then x else undefined)) B"
assume "(∀x∈A. ∀y∈A. f x = f y ⟶ x = y)"
have "?g∘f = restrict id B"
proof
(* cannot prove this *)
end
In above, I try to give an explicit existential witness (i.e. the inverse function g of the original function f). I have several issues about the proof.
whether the concepts are defined right (functions, inverse functions etc.) in Isabelle terms.
how to expand the relevant definitions and then simplify them with function applications. I have followed some Isabelle (2021) examples/tutorials about both the apply-style simp, and structured style Isar proof but couldn't use the Isar proof fluently. Once I started the proof command, I don't know how to simp or move any further.
Isar has the new way of assumes ... shows ... for stating the theorem. Is there similar support for proving iff's (⟷) like the example above? Without it, there is no access to assms etc., and is it necessary to assume everything except the conclusion during the proof.
Can someone help explain how the above existential proof about inverse function can be accomplished?
lemma bij_iff_ex_identity : "bij_betw f A B ⟷ (∃ g. g∘f = restrict id B ∧ f∘g = restrict id A)"
I think this is not exactly what you want an I am doubtful that it is true. g∘f = restrict id B does not mean that g∘f and id are equal on B. It means that the total function g∘f (and there are only total functions in HOL) equals the total function restrict id B. The latter returns id x on x∈B and undefined otherwise. So to make this equality true, g needs to output undefined whenever the input of f is not in B. But how would g know that!
If you want to use restrict, you could write restrict (g∘f) B = restrict id B. But personally, I would rather go for the simpler (∀x∈B. (g∘f) x = x).
So the corrected theorem would be:
lemma bij_iff_ex_identity : "bij_betw f A B ⟷ (∃ g. (∀x∈A. (g∘f) x = x) ∧ (∀y∈B. (f∘g) y = y))"
(Which is still wrong, by the way, as quickcheck tells me in Isabelle/jEdit, see the output window. If A has one element and B is empty, f cannot be a bijection. So the theorem you are attempting is actually mathematically not true. I will not attempt to fix it, but just answer the remaining lines.
unfolding bij_betw_def inj_on_def restrict_def iffI
The iffI here has no effect. Unfolding can only apply theorems of the form A = B (unconditional rewriting rules). iffI is not of that form. (Use thm iffI to see.)
proof
Personally, I don't use the bare form proof but always proof - or proof (some method). Because proof just applies some default method (in this case, equivalent to (rule iffI), so I think it's better to make it explicit. proof - just starts the proof without applying an extra method.
let ?g = "restrict (λ y. (if f x = y then x else undefined)) B"
You have an unbound variable x here. (Note the background color in the IDE.) That is most likely not what you want. Formally, it is allowed, but x will be treated as if it was some arbitrary constant.
Generally, I don't think there is any way to define g in a simple way (i.e., only with quantifiers and function applications and if-then-else). I think the only way to define an inverse (even if you know it exists), is to use the THE operator, because you need to say something like g y is "the" x such that f x = y. (And then later in the proof you will run into a proof obligation that it indeed exists and that it is unique.) See the definition of inv_into in Hilbert_Choice.thy (except it uses SOME not THE). Maybe for starters, try to do the proof just using the existing inv_into constant.
assume "(∀x∈A. ∀y∈A. f x = f y ⟶ x = y)"
All assume commands must have assumptions exactly as the are in the proof goal. You can test whether you wrote it right by just temporarily writing the command show A for A (that's an unprovable goal that would, however, finish the proof, so it tricks Isabelle into checking if it would). If this command does not give an error, you got the assumes right. In your cases, you didn't, it should be (∀x∈A. ∀y∈A. f x = f y ⟶ x = y) ∧ f ' A = B. (' is the backtick symbol here. Markup doesn't let me write it.)
My recommendation: Try the proof with bij instead of bij_betw first. (One direction is in BNF_Fixpoint_Base.o_bij if you want to cheat.)
Once done, you can try to generalize.
I agree with the insightful remarks provided by Dominique Unruh. However, I would like to mention that a theorem that captures the idea underlying the theorem that you are trying to prove already exists in the source code of the main library of Isabelle/HOL. In fact, it exists in at least two different formats: let me name them the traditional Isabelle/HOL format and the canonical FuncSet format. For the former one, see the theorem bij_betw_iff_bijections:
"bij_betw f A B ⟷ (∃g. (∀x ∈ A. f x ∈ B ∧ g(f x) = x) ∧ (∀y ∈ B. g y ∈ A ∧ f(g y) = y))"
The situation is a little bit more complicated with FuncSet. There does not seem to exist a single theorem that captures the idea. However, together, the theorems bij_betwI, bij_betw_imp_funcset and inv_into_funcset are nearly equivalent to the theorem that you are trying to state. Let me provide a sketch of how one could express this theorem in a manner that would be considered reasonably canonical in the FuncSet sense (try to prove it yourself):
lemma bij_betw_iff:
shows "bij_betw f A B ⟷
(
∃g.
(∀x. x∈A ⟶ g (f x) = x) ∧
(∀y. y∈B ⟶ f (g y) = y) ∧
f ∈ A → B ∧
g ∈ B → A
)"
sorry
I would also like to repeat the advice given by Dominique Unruh and provide several side remarks:
My recommendation: Try the proof with bij instead of bij_betw first.
Indeed, this is a very good idea. In general, by trying to restrict the problem to explicitly defined sets A and B, instead of working directly with types, you touched upon a topic that is known as relativization in logic. For a mild layman's introduction see, for example, https://leanprover.github.io/logic_and_proof/first_order_logic.html [1], for a slightly more thorough introduction in the context of set theory see [2, chapter 12]. As you have probably noticed by now, it is not that easy to relativize theorems in Isabelle/HOL and requires additional proof effort.
However, there exists an extension of Isabelle/HOL that allows for the automation of the process of the relativization of theorems. For more information about this extension see the article From Types to Sets by Local Type Definition in Higher-Order Logic by Ondřej Kunčar and Andrei Popescu [3]. There also exists a large scale application example of the framework [4]. Independently, I am working on making this extension more user-friendly and very slowly approaching the final stages in my efforts: see https://gitlab.com/user9716869/tts_extension. Thus, in principle, if you know how to use Types-To-Sets and you accept its axioms, then it is sufficient to prove the theorem with bij, e.g.,
"bij f ⟷ (∃g. (∀x. g (f x) = x) ∧ (∀y. f (g y) = y))",
Then, the theorems like
bij_betw_iff_bijections and bij_betw_iff can be synthesized automatically for free upon a click of a button (almost...).
Finally, for completeness, let me offer my own advice with regard to your queries (although, as I mentioned, I agree with everything stated by Dominique Unruh)
how to expand the relevant definitions and then simplify them with
function applications. I have followed some Isabelle (2021)
examples/tutorials about both the apply-style simp, and structured
style Isar proof but couldn't use the Isar proof fluently. Once I
started the proof command, I don't know how to simp or move any
further.
I believe that the best way to learn what you are trying to learn is by following through the exercises in the book Concrete Semantics by Tobias Nipkow and Gerwin Klein [5]. Additionally, I would also look through A Proof Assistant for Higher-Order Logic by Tobias Nipkow et al [6](it is slightly outdated, but I found it to be useful specifically for learning apply-style scripting/direct rule application). By the way, I have mostly self-taught myself Isabelle from these books without any prior experience in formal methods.
Isar has the new way of assumes ... shows ... for stating the theorem.
Is there similar support for proving iff's (⟷) like the example above?
Without it, there is no access to assms etc., and is it necessary to
assume everything except the conclusion during the proof.
I will make the advice given by Dominique Unruh more explicit: use rule iffI or intro iffI for this.
Edit. When you use rule iffI (or similar) to start your structured Isar proof, you need to state your assumptions explicitly for every subgoal (using the assume ... show ... paradigm). However, there is a tool that can generate such boilerplate Isar code automatically. It is called Sketch-and-Explore and you can find it in the directory HOL/ex of the main library of Isabelle/HOL. In this case, all you need to do is to type sketch(rule iffI) and the assume/show paradigm will be generated automatically for every subgoal.
References
Avigad J, Lewis RY, and van Doorn F. Logic and Proof.
Jech T. Set theory. 3rd ed. Heidelberg: Springer; 2006. (Pure and applied mathematics, a series of monographs and textbooks).
Kunčar O, Popescu A. From Types to Sets by Local Type Definition in Higher-Order Logic. Journal of Automated Reasoning. 2019;62(2):237–60.
Immler F, Zhan B. Smooth Manifolds and Types to Sets for Linear Algebra in Isabelle/HOL. In: 8th ACM SIGPLAN International Conference on Certified Programs and Proofs. New York: ACM; 2019. p. 65–77. (CPP 2019).
Nipkow T, Klein G. Concrete Semantics with Isabelle/HOL. Heidelberg: Springer-Verlag; 2017. (http://concrete-semantics.org/)
Nipkow T, Paulson LC, Wenzel M. A Proof Assistant for Higher-Order Logic. Heidelberg: Springer-Verlag; 2017.

request clarification on appearance of apparent real coercion in theory involving natural numbers in Isabelle theory

I am examining the following theory in Isabelle2020 /jEdit:
theory Sqrt
imports Complex_Main "HOL-Computational_Algebra.Primes"
begin
theorem
assumes "prime (p::nat)"
shows "sqrt p ∉ ℚ"
proof
from ‹prime p› have p: "1 < p" by (simp add: prime_nat_iff)
assume "sqrt p ∈ ℚ"
then obtain m n :: nat where
n: "n ≠ 0" and sqrt_rat: "¦sqrt p¦ = m / n"
and "coprime m n" by (rule Rats_abs_nat_div_natE)
[we omit the remainder of the proof]
The Output pane shows proof state:
have (⋀m n. n ≠ 0 ⟹ ¦sqrt (real p)¦ = real m / real n ⟹ coprime m n ⟹ ?thesis) ⟹ ?thesis
proof (state)
this:
n ≠ 0
¦sqrt (real p)¦ = real m / real n
coprime m n
goal (1 subgoal):
1. sqrt (real p) ∈ ℚ ⟹ False
My question is: Are those appearances of "real" a type coercion? I have read Chapter 8 discussing types in the so-called tutorial that accompanies the Isabelle distribution (title A Proof Assistant for Higher-Order Logic). I read Florian Haftman's document title Isabelle/HOL type-class hierarchy (also part of the Isabelle distribution). The rule used in the theory statements above, Rats_abs_nat_div_natE, is a lemma in the Real.thy theory.
I chased down the reference in that theory file and looked at §8.4.5 in A Proof
Assistant for Higher-Order Logic where I found that The natural
number type nat is a linearly ordered semiring, type int is an ordered ring,
and type real is an ordered field. Properties may not hold for a particular class, e.g., no abstract properties involving subtraction hold for type nat (since, of course, one might end up with a negative number, which would not be a natural number). Instead specific theorems are provided addressing subtraction on the type nat. More to the point, “all abstract properties involving division require a field." (A Proof Assistant for Higher-Order Logic.)
So, are we are seeing here a quotient type being used to lift a division of natural or integer types to the abstract real type in order to satisfy the field
requirement (see §11.9 The Isabelle/Isar Reference Manual)? The quotient type real is created from the equivalence relation definition realrel in the Real.thy file.
I was surprised to see real terms in a proof depending on primes, positive integers, and rational numbers and wanted to assure that I had at least gotten close to the explanation why this is occuring in the Isabelle proof.
The function sqrt is only defined over reals. Therefore, you need to convert its argument p from nat to real. There is a coercion that does that automatically for you; hence the real function you can.
After that, the only way to type m/n is real m / real n.
Generally, overloaded syntax is a problematic for proof assistants. For example, 2/3 on paper can be the rational number Fract 2 3 in Isabelle, the real number 2/3, or the inverse of 3 in a F_5 multiplied by two, or something else.
In Isabelle this is solved by (to a certain extend) avoiding overloading and using different notations.

How to define a data type with constraints?

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.

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