Zulip Chat Archive

Stream: new members

Topic: How to combine type classes?


Marcus Rossel (Apr 06 2023 at 13:44):

What is the general mechanism for using definitions/theorems which expect a given type to have instances for multiple type classes with a shared ancestor? This problem must come up in mathlib's algebraic hierarchy all the time, so I bet there's a good solution.

Say I have the following type classes:

class A (α : Type) where
  nat : α  Nat

class B₁ (α) extends A α where
  lt₉ :  a, nat a < 9

class B₂ (α) extends A α where
  gt₁ :  a, nat a > 1

class B₃ (α) extends A α where
  gt₅ :  a, nat a < 5

class C (α) extends B₁ α, B₂ α, B₃ α

Now if I have a type with instances for B₁ and B₂ I should be able to combine their properties. The naive approach doesn't work:

open A B₁ B₂

example [B₁ α] [B₂ α] (a : α) : (nat a < 9)  (nat a > 1) :=
  lt₉ a, gt₁ a
/-
application type mismatch
  And.intro (lt₉ a)
argument
  lt₉ a
has type
  @nat α B₁.toA a < 9 : Prop
but is expected to have type
  @nat α B₂.toA a < 9 : Prop
-/

I understand that I could fix this by explicitly telling Lean which instances to use for each application of nat, but of course the problem is that I would like the instances of B₁ α and B₂ α to have the same underlying instance of A α.
From what I've seen, the solution for this is to explicitly create a class which combines the two classes:

class B₁₂ (α) extends B₁ α, B₂ α

theorem range₁₉ [B₁₂ α] (a : α) : (nat a < 9)  (nat a > 1) :=
  lt₉ a, gt₁ a -- works

It seems like this would lead to a combinatorial explosion once you have many type classes that you want to combine in various ways, but I can live with that. What I can't figure out though is how to handle the following:

example [C α] (a : α) : (nat a < 9)  (nat a > 1) :=
  range₁₉ -- failed to synthesize instance B₁₂ ?m.684

It makes sense that since C is based on B₁, B₂ and B₃ instead of B₁₂ and B₃, Lean can't find an instance for B₁₂. Now I could change the definition of C to use B₁₂ instead of B₁ and B₂, but then what do I do if I have a theorem over B₂₃?

Yaël Dillies (Apr 06 2023 at 14:02):

You're missing an instance C α → B₁₂ α.

Yaël Dillies (Apr 06 2023 at 14:04):

It seems that your confusion stems from the fact that you think that the only typeclasses used for TC search are the ones coming from extends clauses. This is (fortunately!) wrong. You can register extra instances after the fact using the instance keyword.

Marcus Rossel (Apr 06 2023 at 14:19):

@Yaël Dillies Thanks!

Adam Topaz (Apr 06 2023 at 14:26):

Just in case it's helpful for you, an alternative approach would be to use mixins, as follows:

class A (α : Type) where
  nat : α  Nat

open A

class B₁ (α) [A α] where
  lt₉ :  a : α, nat a < 9

class B₂ (α) [A α] where
  gt₁ :  a : α, nat a > 1

class B₃ (α) [A α] where
  gt₅ :  a : α, nat a < 5

open B₁ B₂ B₃

theorem range₁₉ [A α] [B₁ α] [B₂ α] (a : α) : (nat a < 9)  (nat a > 1) :=
  lt₉ a, gt₁ a -- works

Last updated: Dec 20 2023 at 11:08 UTC