Zulip Chat Archive

Stream: lean4

Topic: type class inference goes haywire


Kevin Buzzard (Jan 21 2023 at 22:36):

The following code (minimised from mathlib, sorry for as ever having to build up a chunk of the alegbra hierarchy)

class Zero (α : Type u) where
  zero : α

instance Zero.toOfNat0 {α} [Zero α] : OfNat α (nat_lit 0) where
  ofNat := Zero α.1

instance Zero.ofOfNat0 {α} [OfNat α (nat_lit 0)] : Zero α where
  zero := 0

class One (α : Type u) where
  one : α

instance One.toOfNat1 {α} [One α] : OfNat α (nat_lit 1) where
  ofNat := One α.1

instance One.ofOfNat1 {α} [OfNat α (nat_lit 1)] : One α where
  one := 1

class Nontrivial (α : Type _) : Prop where
  exists_pair_ne :  x y : α, x  y

class MulZeroClass (M₀ : Type _) extends Mul M₀, Zero M₀ where
  zero_mul :  a : M₀, 0 * a = 0
  mul_zero :  a : M₀, a * 0 = 0

class AddSemigroup (G : Type u) extends Add G where
  add_assoc :  a b c : G, a + b + c = a + (b + c)

class Semigroup (G : Type u) extends Mul G where
  mul_assoc :  a b c : G, a * b * c = a * (b * c)

class CommSemigroup (G : Type u) extends Semigroup G where
  mul_comm :  a b : G, a * b = b * a

class AddCommSemigroup (G : Type u) extends AddSemigroup G where
  add_comm :  a b : G, a + b = b + a

class SemigroupWithZero (S₀ : Type _) extends Semigroup S₀, MulZeroClass S₀

class MulOneClass (M : Type u) extends One M, Mul M where
  one_mul :  a : M, 1 * a = a
  mul_one :  a : M, a * 1 = a

class AddZeroClass (M : Type u) extends Zero M, Add M where
  zero_add :  a : M, 0 + a = a
  add_zero :  a : M, a + 0 = a

class MulZeroOneClass (M₀ : Type u) extends MulOneClass M₀, MulZeroClass M₀

class AddMonoid (M : Type u) extends AddSemigroup M, AddZeroClass M where

class Monoid (M : Type u) extends Semigroup M, MulOneClass M where

structure Units (α : Type u) [Monoid α] where
  val : α
  inv : α
  val_inv : val * inv = 1
  inv_val : inv * val = 1

postfix:1024 "ˣ" => Units

class Inv (α : Type u) where
  inv : α  α

postfix:max "⁻¹" => Inv.inv

instance [Monoid α] : Inv αˣ :=
  fun u => u.2, u.1, u.4, u.3⟩⟩

instance [Monoid α] : CoeHead αˣ α :=
  Units.val

def divp [Monoid α] (a : α) (u : Units α) : α :=
  a * (u⁻¹ : αˣ)

infixl:70 " /ₚ " => divp

class MonoidWithZero (M₀ : Type u) extends Monoid M₀, MulZeroOneClass M₀, SemigroupWithZero M₀

class LeftCancelSemigroup (G : Type u) extends Semigroup G where
  mul_left_cancel :  a b c : G, a * b = a * c  b = c

class LeftCancelMonoid (M : Type u) extends LeftCancelSemigroup M, Monoid M

class RightCancelSemigroup (G : Type u) extends Semigroup G where
  mul_right_cancel :  a b c : G, a * b = c * b  a = c

class RightCancelMonoid (M : Type u) extends RightCancelSemigroup M, Monoid M

class AddCommMonoid (M : Type u) extends AddMonoid M, AddCommSemigroup M

class SubNegMonoid (G : Type u) extends AddMonoid G, Neg G, Sub G where
  sub a b := a + -b
  sub_eq_add_neg :  a b : G, a - b = a + -b := by intros; rfl

class AddGroup (A : Type u) extends SubNegMonoid A where
  add_left_neg :  a : A, -a + a = 0

class AddMonoidWithOne (R : Type u) extends AddMonoid R, One R where

class CommMonoid (M : Type u) extends Monoid M, CommSemigroup M

class AddGroupWithOne (R : Type u) extends AddMonoidWithOne R, AddGroup R where

class AddCommGroup (G : Type u) extends AddGroup G, AddCommMonoid G

class Distrib (R : Type _) extends Mul R, Add R where
  protected left_distrib :  a b c : R, a * (b + c) = a * b + a * c
  protected right_distrib :  a b c : R, (a + b) * c = a * c + b * c

class NonUnitalNonAssocSemiring (α : Type u) extends AddCommMonoid α, Distrib α, MulZeroClass α

class AddCommMonoidWithOne (R : Type _) extends AddMonoidWithOne R, AddCommMonoid R

class NonAssocSemiring (α : Type u) extends NonUnitalNonAssocSemiring α, MulZeroOneClass α,
    AddCommMonoidWithOne α

class NonUnitalSemiring (α : Type u) extends NonUnitalNonAssocSemiring α, SemigroupWithZero α

class Semiring (α : Type u) extends NonUnitalSemiring α, NonAssocSemiring α, MonoidWithZero α

class Ring (R : Type u) extends Semiring R, AddCommGroup R, AddGroupWithOne R

class CommRing (α : Type u) extends Ring α, CommMonoid α

theorem divp_eq_iff_mul_eq [Monoid α] {x : α} {u : αˣ} {y : α} : x /ₚ u = y  y * u = x := sorry

theorem divp_mul_eq_mul_divp {α : Type u} [CommMonoid α] (x y : α) (u : αˣ) :
    x /ₚ u * y = x * y /ₚ u := sorry

@[simp] theorem mul_right_eq_self {M : Type u} [inst : LeftCancelMonoid M] {a b : M} :
    a * b = a  b = 1 := sorry

variable {R : Type _} [CommRing R] (a b : R) (u₁ u₂ : Rˣ)

set_option trace.Meta.synthInstance true in
set_option trace.Meta.Tactic.simp true in
example : a /ₚ u₁ = b /ₚ u₂  a * u₂ = b * u₁ := by
  simp [divp_eq_iff_mul_eq, divp_mul_eq_mul_divp]

has a completely innocuous-looking trace until the simplifier decides to try applying the simp lemma mul_right_eq_self. At this point it then does this

(over 350 instances of `[Meta.synthInstance]  LeftCancelMonoid R ▶` omitted)

[Meta.synthInstance]  LeftCancelMonoid R 

[Meta.synthInstance]  LeftCancelMonoid R 

[Meta.synthInstance]  LeftCancelMonoid R 

[Meta.synthInstance]  LeftCancelMonoid R 

[Meta.synthInstance]  LeftCancelMonoid R 

[Meta.Tactic.simp.unify] @mul_right_eq_self:1000, failed to unify
      ?a * ?b = ?a
    with
      a * u₂.val = b * u₁.val `

Unfolding some of the 384 failures to synthesize LeftCancelMonoid R indicates that they all seem to be

[Meta.synthInstance]  LeftCancelMonoid R 
  [] no instances for LeftCancelMonoid R 
    [instances] #[]

Of course in the real world example in mathlib they are much more complicated, because there are instances for this class, and the failures take longer to happen, leading to timeouts. The mathlib4 thread discussing this is here. This is surely a bug?

Kevin Buzzard (Jan 22 2023 at 00:48):

I just chose a random file in mathlib, switched on instance tracing and found a lemma with a three-line proof involving simp which (correctly) synthesized CommSemigroup α 86 times. This was in the first file I looked at. No idea if it's related though.

Gabriel Ebner (Jan 22 2023 at 01:21):

On a high level, this is a similar issue as lean4#1986. We try to unify the HMul R R R instance in the lemma (derived from an unknown LeftCancelMonoid instance) with the HMul R R R instance in the goal (derived from the CommRing instance in the context).

David Renshaw (Jan 22 2023 at 01:22):

( lean4#1986 presumeably)

Gabriel Ebner (Jan 22 2023 at 01:27):

We try many things during unification, and many of them multiple times. For example I can see that we visit the subgoal Monoid.toMulOneClass =?= NonUnitalSemiring.toNonUnitalNonAssocSemiring at least three times (and then there's probably a subgoal in there that gets visited 3*3 times, etc.). And whenever we inevitably fail to unify these subgoals, we try to instantiate the missing LeftCancelMonoid instance using type class synthesis.

Gabriel Ebner (Jan 22 2023 at 01:29):

lean4#2003 doesn't solve this problem either, because projections on metavariables don't reduce. :sad:

David Renshaw (Jan 22 2023 at 01:32):

It looks to me like all those synthInstance calls are happening in a single isDefEq call here: https://github.com/leanprover/lean4/blob/a125a36bcc79a28963ed5786f94c5d97648a8f99/src/Lean/Meta/Tactic/Simp/Rewrite.lean#L57

David Renshaw (Jan 22 2023 at 01:33):

with lhs as ?a * ?b = ?a
and e as a * u₂.val = b * u₁.val

David Renshaw (Jan 22 2023 at 01:43):

a lot more info shows up with
set_option trace.Meta.isDefEq true

James Gallicchio (Jan 22 2023 at 12:23):

would it be feasible to cache the unification results that happen in a single isDefEq check? or would the cost of that outweigh the savings

Kevin Buzzard (Jan 23 2023 at 18:53):

lean4#2055


Last updated: Dec 20 2023 at 11:08 UTC