Zulip Chat Archive
Stream: lean4
Topic: type class inference looping
Kevin Buzzard (Jan 03 2023 at 00:18):
@Heather Macbeth ran into an issue in the mathlib port with typeclass inference looping. I minimised it, and it's quite surprising: if you don't tell Lean alpha
explicitly, by the time it's figured out you mean alpha
it seems to have forgotten that alpha
is a MonoidWithZero.
universe u v w
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 MulZeroClass (M₀ : Type _) extends Mul M₀, Zero M₀ where
zero_mul : ∀ a : M₀, 0 * a = 0
mul_zero : ∀ a : M₀, a * 0 = 0
class Semigroup (G : Type u) extends Mul G where
mul_assoc : ∀ a b c : G, a * b * c = a * (b * c)
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 MulZeroOneClass (M₀ : Type u) extends MulOneClass M₀, MulZeroClass M₀
class Monoid (M : Type u) extends Semigroup M, MulOneClass M where
class MonoidWithZero (M₀ : Type u) extends Monoid M₀, MulZeroOneClass M₀, SemigroupWithZero M₀
class SMul (M : Type _) (α : Type _) where
smul : M → α → α
class HSMul (α : Type u) (β : Type v) (γ : outParam (Type w)) where
hSMul : α → β → γ
instance instHSMul (M : Type _) (α : Type _) [SMul M α] : HSMul M α α where
hSMul := SMul.smul
infixr:73 " • " => HSMul.hSMul
class MulAction (α : Type _) (β : Type _) [Monoid α] extends SMul α β where
protected one_smul : ∀ b : β, (1 : α) • b = b
mul_smul : ∀ (x y : α) (b : β), (x * y) • b = x • y • b
variable (R M)
class MulActionWithZero [MonoidWithZero R] [Zero M] extends MulAction R M where
smul_zero : ∀ r : R, r • (0 : M) = 0
zero_smul : ∀ m : M, (0 : R) • m = 0
class SMulZeroClass (M A : Type _) [Zero A] extends SMul M A where
smul_zero : ∀ a : M, a • (0 : A) = 0
class SMulWithZero [Zero R] [Zero M] extends SMulZeroClass R M where
zero_smul : ∀ m : M, (0 : R) • m = 0
instance (priority := 100) MulActionWithZero.toSMulWithZero [MonoidWithZero R] [Zero M] [m : MulActionWithZero R M] :
SMulWithZero R M :=
{ m with }
variable {I : Type u}
variable {f : I → Type v}
namespace Pi
instance instZero [∀ i, Zero <| f i] : Zero (∀ i : I, f i) :=
⟨fun _ => 0⟩
variable {M}
instance instSMul [∀ i, SMul M <| f i] : SMul M (∀ i : I, f i) :=
⟨fun s x => fun i => s • x i⟩
instance smulWithZero [Zero α] [∀ i, Zero (f i)] [∀ i, SMulWithZero α (f i)] :
SMulWithZero α (∀ i, f i) :=
{ Pi.instSMul with
-- already this is odd:
-- smul_zero := fun a => funext fun i => SMulZeroClass.smul_zero a -- "failed to synthesize instance `SMulZeroClass α (f i)`"
smul_zero := fun a => funext fun i => @SMulZeroClass.smul_zero _ (f i) _ _ a -- needs the @
zero_smul := fun _ => funext fun _ => SMulWithZero.zero_smul _ }
instance mulAction (α) {m : Monoid α} [∀ i, MulAction α <| f i] :
@MulAction α (∀ i : I, f i) m where
smul := (· • ·)
mul_smul _ _ _ := funext fun _ => MulAction.mul_smul _ _ _
one_smul _ := funext fun _ => MulAction.one_smul _
set_option trace.Meta.synthInstance true
set_option maxHeartbeats 10000 -- consider turning trace.Meta.synthInstance to false if you
-- make this number much bigger
-- works fine
instance mulActionWithZero (α) [MonoidWithZero α] [∀ i, Zero (f i)]
[∀ i, MulActionWithZero α (f i)] : MulActionWithZero α (∀ i, f i) :=
{ Pi.mulAction α with
smul_zero := Pi.smulWithZero.smul_zero
zero_smul := Pi.smulWithZero.zero_smul }
-- also works
instance mulActionWithZero' (α) [MonoidWithZero α] [∀ i, Zero (f i)]
[∀ i, MulActionWithZero α (f i)] : MulActionWithZero α (∀ i, f i) :=
{ Pi.mulAction α, @Pi.smulWithZero _ _ α _ _ _ with }
-- but this seems to be looping
instance mulActionWithZero' (α) [MonoidWithZero α] [∀ i, Zero (f i)]
[∀ i, MulActionWithZero α (f i)] : MulActionWithZero α (∀ i, f i) :=
{ Pi.mulAction α, Pi.smulWithZero with }
/-
Lean claims to not be able to find `MonoidWithZero α` even though it's explicitly given as an instance.
[Meta.synthInstance] 💥 (i : I) → SMulWithZero α (f i) ▼
[] new goal (i : I) → SMulWithZero α (f i) ▶
[] 💥 apply MulActionWithZero.toSMulWithZero to (i : I) → SMulWithZero α (f i) ▼
[tryResolve] 💥 SMulWithZero α (f i) ≟ SMulWithZero (?m.15131 i) (?m.15132 i) ▼
[] ❌ Zero α ▼
[] new goal Zero α ▶
[] ✅ apply @MonoidWithZero.toZero to Zero α ▼
[tryResolve] ✅ Zero α ≟ Zero α
[] no instances for MonoidWithZero α ▶
[] ✅ apply @AddMonoid.toZero to Zero α ▶
[] ✅ apply @MulZeroOneClass.toZero to Zero α ▶
[] ✅ apply @MonoidWithZero.toMulZeroOneClass to MulZeroOneClass α ▶
[] ✅ apply @AddZeroClass.toZero to Zero α ▶
[] ✅ apply @AddMonoid.toAddZeroClass to AddZeroClass α ▶
[] ✅ apply @SemigroupWithZero.toZero to Zero α ▶
[] ✅ apply @MonoidWithZero.toSemigroupWithZero to SemigroupWithZero α ▶
[] ✅ apply @MulZeroClass.toZero to Zero α ▶
[] ✅ apply @MulZeroOneClass.toMulZeroClass to MulZeroClass α ▶
[] ✅ apply @SemigroupWithZero.toMulZeroClass to MulZeroClass α ▶
[] ✅ apply @Zero.ofOfNat0 to Zero α ▶
[] ✅ apply @Zero.toOfNat0 to OfNat α 0 ▶
[] ✅ I → MonoidWithZero α ▶
-/
Heather Macbeth (Jan 03 2023 at 00:22):
Thanks for bringing this to #lean4 Kevin. I think I've minimized it a bit more actually:
variable {α : Type}
variable {R : Type}
class Zip (α : Type) -- represents `Zero`
class SMul (R : Type) (α : Type) where
smul : R → α → α
infixr:73 " • " => SMul.smul
class MulAction (R : Type) (β : Type) extends SMul R β
class SMulZeroClass (R α : Type) extends SMul R α where
smul_zero : ∀ r : R, ∀ a : α, r • a = a
class MulActionWithZero (R α : Type) extends MulAction R α, SMulZeroClass R α
class SMulWithZero (R α : Type) [Zip R] [Zip α] extends SMulZeroClass R α
instance MulActionWithZero.toSMulWithZero (R M : Type)
[Zip R] [Zip M] [m : MulActionWithZero R M] :
SMulWithZero R M :=
{ m with }
namespace pi
variable {I : Type}
variable {f : I → Type}
instance instZero [∀ i, Zip (f i)] : Zip (∀ i : I, f i) := ⟨⟩
instance instSMul [∀ i, SMul R (f i)] : SMul R (∀ i : I, f i) :=
⟨fun s x => fun i => s • x i⟩
instance smulWithZero (R) [Zip R] [∀ i, Zip (f i)] [∀ i, SMulWithZero R (f i)] :
SMulWithZero R (∀ i, f i) :=
{ pi.instSMul with
smul_zero := sorry }
instance mulAction (R) [∀ i, MulAction R (f i)] : MulAction R (∀ i : I, f i) where
smul := (· • ·)
set_option trace.Meta.synthInstance true
set_option maxHeartbeats 10000 -- consider turning trace off if you make this much bigger
-- seems to be looping
instance mulActionWithZero (R) [Zip R] [∀ i, Zip (f i)] [∀ i, MulActionWithZero R (f i)] :
MulActionWithZero R (∀ i, f i) :=
{ pi.mulAction _, pi.smulWithZero _ with }
end pi
Heather Macbeth (Jan 03 2023 at 00:23):
And I checked that the error doesn't appear for the corresponding code in Lean 3.
Heather Macbeth (Jan 03 2023 at 00:23):
But i'm not sure how to read the typeclass inference trace.
Moritz Firsching (Jan 03 2023 at 13:14):
I'm not entirely sure, but I think I have observed the same behavior when porting Mathlib/Data/Set/Intervals/Instances.lean
Data/Set/Intervals/Instances.lean#L342-L344
Here one also gets an deterministic time out and the structure of the instance is very similar
Gabriel Ebner (Jan 03 2023 at 17:49):
After taking a quick look, this slightly confusing behavior is due to nested TC resolution. If you enable the trace.Meta.isDefEq
option, then you'll see that the failing type class inference happens during unification. The thing is, we only allow a single layer of nesting (so tc->defeq->tc is fine, but defeq->tc->defeq->tc is not). So if you have a type class instance that requires nested TC resolution during TC synthesis (like the one here), then it won't be found during unification (i.e., when trying to fill in the underscore here).
Heather Macbeth (Jan 03 2023 at 19:25):
Thanks for the information Gabriel. Do you know why this feature was supported in Lean 3 but not in Lean 4?
Gabriel Ebner (Jan 03 2023 at 19:41):
What I said is correct, but after some investigation it's not the issue here (although it might bite us in the future). From what I can tell, all we're missing here is a !
. https://github.com/leanprover/lean4/commit/70a6c06eef060d8e14869e2848749287e5364742
Kevin Buzzard (Jan 03 2023 at 20:24):
Does this change have any effect on the examples at lean4#1986?
Gabriel Ebner (Jan 03 2023 at 20:29):
Not as far as I can tell.
Kevin Buzzard (Jan 03 2023 at 20:40):
docs4#Pi.mulAction has {m : Monoid α}
a typeclass variable being inferred by unification. Presumably this is intentional? Is this related?
Kevin Buzzard (Jan 03 2023 at 20:43):
And does it change this?
import Mathlib.Algebra.SMulWithZero
universe u v
variable {I : Type u}
variable {f : I → Type v}
set_option trace.Meta.synthInstance true
instance Pi.smulWithZero (α) [Zero α] [∀ i, Zero (f i)] [∀ i, SMulWithZero α (f i)] :
SMulWithZero α (∀ i, f i) :=
{ Pi.instSMul with
smul_zero := fun a => funext fun i => SMulZeroClass.smul_zero a -- fails with error
/-
failed to synthesize instance
SMulZeroClass α (f i)
-/
zero_smul := sorry }
Kevin Buzzard (Jan 03 2023 at 20:46):
smul_zero := fun a => funext fun i => @SMulZeroClass.smul_zero α (f i) _ _ a -- works fine
Kevin Buzzard (Jan 03 2023 at 21:06):
I see this in the traces of Heather's example:
[Meta.isDefEq] 💥 ∀ (r : R) (a : (i : I) → f i), r • a = a =?= ∀ (r : R) (a : (i : I) → f i), r • a = a
Is this surprising?
Gabriel Ebner (Jan 03 2023 at 21:57):
The bomb in synthInstance usually means that TC synthesis aborted because it saw a metavariable. For example, you need to find an instance of Group (?G, ?H)
. Then we abort and try again later when ?G
is filled in.
Kevin Buzzard (Jan 04 2023 at 00:32):
But the bomb in the above had no metavariables I think
Sebastian Ullrich (Jan 04 2023 at 09:18):
This is the example with the heartbeat timeout, right? As soon as the limit is reached, any further isDefEq/whnf/... will bomb out in 0 steps. synthInstance is the only subsystem with a per-invocation limit.
Sebastian Ullrich (Jan 04 2023 at 09:18):
But also, image.png
Henrik Böving (Jan 04 2023 at 09:24):
How are you getting these type hovers in emacs in the info view? @Sebastian Ullrich
Sebastian Ullrich (Jan 04 2023 at 09:28):
By using VS Code :)
Henrik Böving (Jan 04 2023 at 09:29):
/o\
Henrik Böving (Jan 04 2023 at 09:29):
So I am the last one left on emacs now huh.
Sebastian Ullrich (Jan 04 2023 at 09:32):
It's basically the only time I use VS Code. But I'm not holding my breath for widget support in Emacs either. I guess using an external web browser window as suggested before is the most realistic solution.
Oliver Nash (Aug 17 2023 at 09:31):
Are typeclass cycles allowed in Lean 4?
Yesterday I tried to promote docs#Module.free_of_finite_type_torsion_free' to an instance
but I failed and I suspect this is because it forms a cycle with docs#Module.Free.noZeroSMulDivisors
I also tried to promote docs#isNoetherian_of_isNoetherianRing_of_finite to an instance
and again I failed and suspect this is because of the cycle it forms with docs#Module.IsNoetherian.finite
I tried to see if I could understand based on reading https://arxiv.org/pdf/2001.04301.pdf but I got lazy and decided I'd just ask here instead.
Scott Morrison (Aug 17 2023 at 10:44):
Yes, cycles should not be a problem.
Oliver Nash (Aug 19 2023 at 12:31):
I claim #6548 is further evidence that cycles are still problematic in Lean 4. I admit this is a bit vague since any new instance of course creates new work for typeclass inference but I think it's worth trying to collect a few examples of cycle-creating instances that are notably costly.
Sebastian Ullrich (Aug 19 2023 at 12:59):
Multiple files were touched, more were probably affected indirectly; out of these, one regressed by ~200ms and another one exceeded a threshold with no idea how close it was before, as already pointed out by Kevin. That is not evidence.
Chris Hughes (Aug 19 2023 at 15:25):
In the file that broke in #6548, the lemma XYIdeal_eq₂
goes from 93846 heartbeats to 109609 after adding attribute [instance] CharP.charP_to_charZero
. The theorem XYIdeal_neg_mul
goes from 803202
to 828585
after adding the same instance. I don't know if count_heartbeats
is the thing we should be using here. ring1
times out looking for a CharZero
instance on F[X][X]
that isn't there.
Kevin Buzzard (Aug 21 2023 at 09:29):
I don't know if that wild goose chase for the CharZero instance is counted by heartbeats? It would probably be synthInstance heartbeats I guess.
Junyan Xu (Aug 25 2023 at 00:40):
I hope this is some better evidence that cycles are still problematic: https://github.com/leanprover-community/mathlib4/issues/6646#issuecomment-1691792488
diff to reproduce and profile
Anne Baanen (Aug 25 2023 at 09:55):
Here's another example of problematic cycles I ran into recently: https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/.60by_cases.60.20trying.20to.20find.20a.20weird.20instance/near/374893214
Jireh Loreaux (Sep 22 2023 at 05:48):
I'm fairly certain the errors I'm encountering here on #7309 are also due to cycle problems. This PR introduces two TC cycles, and it seems the latter (for OrderedCommRing
) is causing the problems. For commit 987689ab10908b46992444e7f5075359ccacfcfd
on the relevant branch#j-loreaux/OrderedStarRing, one can do:
import Mathlib
-- without disabling this instance, the `inferInstance` call fails below.
-- if it's disabled, Lean finds the instance very quickly
attribute [-instance] StarOrderedRing.toOrderedCommRing
example {R : Type*} [CommRing R] (I : Ideal R) (f : Polynomial R) : True := by
letI : CommSemiring (AdjoinRoot f ⧸ Ideal.map (Ideal.Quotient.mk (Ideal.span {f}))
(Ideal.map Polynomial.C I)) := inferInstance
-- even with the instance above activated `CommRing.toCommSemiring` works.
trivial
Note: it's getting caught up in isDefEq
and it's the maxHeartbeats
timing out (not synthInstance.maxHeartbeats
). The TC cycles I added don't actually change the data fields, but I think the issue may be that Lean is doing a lot of unbundling / rebundling because of new style structures, and then it can't unify.
Last updated: Dec 20 2023 at 11:08 UTC