4

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"
Denis
  • 1,167
  • 1
  • 10
  • 30

2 Answers2

3

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é Thiemann
  • 1,251
  • 6
  • 2
2

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.

Manuel Eberl
  • 7,858
  • 15
  • 24