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.
Related
The following expressions are almost identical:
value "(1,5) ∈ trancl {(1::nat,2::nat),(2,5)}"
value "(1,5) ∈ rtrancl {(1::nat,2::nat),(2,5)}"
However the first one is evaluated fine, and for the second one I get the following error:
Wellsortedness error:
Type nat not of sort {enum,equal}
No type arity nat :: enum
It seems that the error is caused by the identity relation:
value "(1::nat,5::nat) ∈ Id"
However the following code lemma doesn't help:
lemma Id_code [code]: "(a, b) ∈ Id ⟷ a = b" by simp
Could you please suggest how to fix it? Why it doesn't work from the scratch? Is it just an incompleteness of code lemmas or there are more fundamental reasons?
The problem becomes apparent when you look at the code equations for rtrancl with code_thms rtrancl:
rtrancl r ≡ trancl r ∪ Id
Here, Id is the identity relation, i.e. the set of all (x, x). If your type is infinite, both Id and its complement will also be infinite, and there is no way to represent that in a set with the Isabelle code generator's default set representation (which is as lists of either all the elements that are in the set or all the elements that are not in the set).
The code equation for Id reflects this as well:
Id = (λx. (x, x)) ` set enum_class.enum
Here, enum_class.enum comes from the enum type class and is a list of all values of a type. This is where the enum constraint comes from when you try to evaluate rtrancl.
I don't think it's possible to evaluate rtrancl without modifying the representation of sets in the code generator setup (and even then, printing the result in a readable form would be challenging). However, getting the code generator to evaluate things like (1, 5) ∈ rtrancl … is relatively easy: you can simply register a code unfold rule like this:
lemma in_rtrancl_code [code_unfold]: "z ∈ rtrancl A ⟷ fst z = snd z ∨ z ∈ trancl A"
by (metis prod.exhaust_sel rtrancl_eq_or_trancl)
Then your value command works fine.
The Isabelle reference manual describes to ways to perform type-based overloading of constants: "Adhoc overloading of constants" in section 11.3, and "Overloaded constant definitions" in section 5.9.
It seems that 5.9 overloading requires all type parameters to be known before it decides on an overloaded constant, whereas 11.3 (adhoc) overloading decides on an overloaded constant if there is only one matching:
consts
c1 :: "'t ⇒ 'a set"
c2 :: "'t ⇒ 'a set"
definition f1 :: ‹'a list ⇒ 'a set› where
‹f1 s ≡ set s›
adhoc_overloading
c1 f1
overloading
f2 ≡ ‹c2 :: 'a list ⇒ 'a set›
begin
definition ‹f2 w ≡ set w›
end
context
fixes s :: ‹int list›
begin
term ‹c1 s› (* c1 s :: int set *)
term ‹c2 s› (* c2 s :: 'a set *)
end
What's the difference between the two? When would I use one over the other?
Overloading is a core feature of Isabelle's logic. It allows you to declare a single constant with a "broad" type that can be defined on specific types. There's rarely a need for users to do that manually. It is the underlying mechanism used to implement type classes. For example, if you define a type class as follows:
class empty =
fixes empty :: 'a
assumes (* ... *)
Then, the class command will declare the constant empty of type 'a', and subsequent instantiations merely provide a definition of empty for specific types, like nat or list.
Long story short: overloading is – for most purposes – an implementation detail that is managed by higher-level commands. Occasionally, the need for some manual tweaking arises, e.g. when you have to define a type that depends on class constraints.
Ad-hoc overloading is, in my opinion, a misleading name. As far as I understand, it stems from Haskell (see this paper from Wadler and Blott). There, they use it to describe precisely the type class mechanism that in Isabelle would be coined as just "overloading". In Isabelle, ad-hoc overloading means something entirely different. The idea is that you can use it to define abstract syntax (like do-notation for monads) that can't accurately be capture by Isabelle's ML-style simple type system. As in overloading, you'd define a constant with a "broad" type. But that constant never receives any definitions! Instead, you define new constants with more specific types. When Isabelle's term parser encounters the use of the abstract constant, it will try to replace it with a concrete constant.
For example: you can use do-notation with option, list, and a few other types. If you write something like:
do { x <- foo; bar }
Then Isabelle sees:
Monad_Syntax.bind foo (%x. bar)
In a second step, depending on the type of foo, it will translate it to one of these possible terms:
Option.bind foo (%x. bar)
List.bind foo (%x. bar)
(* ... more possibilities ...*)
Again, users probably don't need to deal with this concept explicitly. If you pull in Monad_Syntax from the library, you'll get one application of ad-hoc overloading readily configured.
Long story short: ad-hoc overloading is a mechanism for enabling "fancy" syntax in Isabelle. Newbies may get confused by it because error messages tend to be hard to understand if there's something wrong in the internal translation.
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.
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.
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.