Zulip Chat Archive

Stream: new members

Topic: Proving a simple Denumerable instance


Graham Leach-Krouse (Mar 06 2023 at 15:40):

Is there a preferred/idiomatic way to prove denumerability for a simple inductive type, like:

inductive Form : Type
  | atom : Nat  Form
  | neg  : Form  Form
  | and  : Form  Form  Form
  | or   : Form  Form  Form
  | impl : Form  Form  Form

for example?

What I'm considering is proving countability and infinity, then using Encodable.ofCountable and Denumerable.ofEncodableOfInfinite. This feels sort of indirect, and ends up requiring a not of noncomputable annotations. But the only alternative that's leaping out at me right now (to build decode : Nat->Form) is to futz around with Nat.mkpair and Nat.evenOddRec or something, which seems likely to be a mess.

Kevin Buzzard (Mar 06 2023 at 17:49):

It would be cool if @[derive Denumerable] could be made to work. I guess you could build an explicit injection i into Nat by sending atom n to 2^(n+1), neg f to 3^(1 + i f), and f g to 5^(1 + b (i f) (i g)) where b is some fixed bijection Nat^2->Nat etc; injectivity should hopefully be a simple induction (modulo the necessary arithmetic lemmas)

Eric Wieser (Mar 06 2023 at 17:54):

You could also use docs#nat.div_mod_equiv to split the type into the 5 constructors, and then use mkpair on the binary constructors

Eric Wieser (Mar 06 2023 at 17:54):

Do we have an n-ary version of mkpair?

Eric Wieser (Mar 06 2023 at 17:54):

I guess you can just iterate it

Kyle Miller (Mar 06 2023 at 18:17):

It seems like you could get a lot done if you have that the following type is Encodable:

inductive ConsExp : Type
  | nat : Nat  ConsExp
  | cons  : Nat  List ConsExp  ConsExp

It's easy to create an injective map to this from Form:

inductive Form : Type
  | atom : Nat  Form
  | neg  : Form  Form
  | and  : Form  Form  Form
  | or   : Form  Form  Form
  | impl : Form  Form  Form

def Form.toConsExp : Form  ConsExp
  | atom n => .cons 0 [.nat n]
  | neg f => .cons 1 [f.toConsExp]
  | and f g => .cons 2 [f.toConsExp, g.toConsExp]
  | or f g => .cons 3 [f.toConsExp, g.toConsExp]
  | impl f g => .cons 4 [f.toConsExp, g.toConsExp]

theorem Form.toConsExp_injective : Function.Injective Form.toConsExp := by
  intros f1 f2
  induction f1 generalizing f2 <;> cases f2 <;> simp! <;> aesop

Kyle Miller (Mar 06 2023 at 18:23):

It's also easy to construct a left inverse:

/-- Interpret a `ConsExp` as a `Form`, returning `atom 0` if the input is malformed. -/
def ConsExp.toForm : ConsExp  Form
  | .cons 0 [.nat n] => .atom n
  | .cons 1 [f] => .neg f.toForm
  | .cons 2 [f, g] => .and f.toForm g.toForm
  | .cons 3 [f, g] => .or f.toForm g.toForm
  | .cons 4 [f, g] => .impl f.toForm g.toForm
  | _ => .atom 0

theorem toForm_toConsExp_eq (f : Form) : f.toConsExp.toForm = f := by
  induction f <;> simp! <;> unfold ConsExp.toForm <;> simp [*]

Graham Leach-Krouse (Mar 06 2023 at 18:46):

Thanks all! I went the countability/infinity route this morning. The injection goes first into a sum of products of Nat, using the Encodable instance for that to recur:

abbrev tupleEquiv := Nat Nat (Nat × Nat) (Nat × Nat) (Nat×Nat)

def Form.toSumEncoding : Form  Nat Nat (Nat × Nat) (Nat × Nat) (Nat×Nat)
  | #n => Sum.inl n
  | ~f => Sum.inr $ Sum.inl (Encodable.encode $ toSumEncoding f)
  | f & g => Sum.inr $ Sum.inr $ Sum.inl Encodable.encode $ toSumEncoding f, Encodable.encode $ toSumEncoding g
  | f ¦ g => Sum.inr $ Sum.inr $ Sum.inr $ Sum.inl Encodable.encode $ toSumEncoding f, Encodable.encode $ toSumEncoding g
  | Form.impl f g => Sum.inr $ Sum.inr $ Sum.inr $ Sum.inr Encodable.encode $ toSumEncoding f, Encodable.encode $ toSumEncoding g

Derivable denumberance instances would be cool. But short of that, the ConsExp idea or something similar seems pretty elegant. When I clean this up, I'll see if I can work something like that out.

Kyle Miller (Mar 06 2023 at 19:09):

@Graham Leach-Krouse As an exercise for myself, I created an Encodable instance for a variant of ConsExpr and used it to create one for Form:

code

Kyle Miller (Mar 06 2023 at 19:09):

The intermediate type is the simpler

inductive ConsExp : Type
  | nat : Nat  ConsExp
  | cons : ConsExp  ConsExp  ConsExp

Eric Wieser (Mar 06 2023 at 19:13):

I think the fintype derive handler alread builds this type of equivalence for you

Eric Wieser (Mar 06 2023 at 19:13):

So we should be able to extend it to encodable quite easily

Kevin Buzzard (Mar 06 2023 at 19:16):

Is it possible to @derive this?

Graham Leach-Krouse (Mar 06 2023 at 19:32):

Trivial tweak: here's a revision using Option.none rather than .atom 0 for the junk case:

def ConsExp.toForm : ConsExp  Option Form
  | .cons (.nat 0) (.nat n) => .some (.atom n)
  | .cons (.nat 1) f => .neg <$> f.toForm
  | .cons (.nat 2) (.cons f g) => .and <$> f.toForm <*> g.toForm
  | .cons (.nat 3) (.cons f g) => .or <$> f.toForm <*> g.toForm
  | .cons (.nat 4) (.cons f g) => .impl <$> f.toForm <*> g.toForm
  | _ => .none

theorem toForm_toConsExp_eq (f : Form) : f.toConsExp.toForm = some f := by
  induction f <;> simp! [*]

instance : Encodable Form where
  encode f := Encodable.encode f.toConsExp
  decode n := Option.bind (Encodable.decode n) ConsExp.toForm
  encodek := by
    intro f
    simp [toForm_toConsExp_eq]

Last updated: Dec 20 2023 at 11:08 UTC