Skip to content

Commit e238e45

Browse files
chore: adapt to 2026-09-29 (#962)
Includes @[grind hom fallback] docs.
1 parent d880b9b commit e238e45

5 files changed

Lines changed: 187 additions & 17 deletions

File tree

‎.vale/styles/config/ignore/terms.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ desugarings
6363
desugars
6464
disambiguator
6565
disambiguators
66+
discharger
67+
dischargers
6668
discriminant
6769
discriminant's
6870
disequality

‎Manual/Grind/EMatching.lean‎

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ grindFunCC
295295
grindFwd
296296
grindGen
297297
grindHom
298+
grindHomFallback
298299
grindHomPred
299300
grindInj
300301
grindIntro
@@ -638,20 +639,6 @@ The {tactic}`grind` tactic can work with a source algebra that doesn't have a gr
638639
{ref "grind-hom"}[Homomorphism rules] describe the injection from source to target, and how the injection commutes with other operations (like addition or multiplication in the case of bitvectors).
639640
Homomorphism predicates present additional facts that {tactic}`grind` can use about the injection (like that a bitvector of length $`n` corresponds to a natural number less than $`2^n`).
640641

641-
:::syntax Lean.Parser.Attr.grindMod (title := "Homomorphism Rules")
642-
```grammar
643-
hom
644-
```
645-
{includeDocstring Lean.Parser.Attr.grindHom}
646-
:::
647-
648-
:::syntax Lean.Parser.Attr.grindMod (title := "Homomorphism Predicates")
649-
```grammar
650-
hom_pred
651-
```
652-
{includeDocstring Lean.Parser.Attr.grindHomPred}
653-
:::
654-
655642

656643
{TODO}[Document `gen` modifier for `grind` patterns]
657644

‎Manual/Grind/Hom.lean‎

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,20 @@ To use the feature at all, the mapping should be injective with respect to equal
170170
That is, given {lean}`x` and {lean}`y` of type {lean}`T`, it should be the case that {lean}`x = y` is logically equivalent to {lean}`x.toU = y.toU`.
171171
Adding the {attr}`grind hom` attribute to a suitable injectivity theorem activates the mapping feature.
172172

173+
:::paragraph
173174
To be useful, the mapping should translate operations of interest on {lean}`T` into operations in {lean}`U` that are supported by {tactic}`grind`'s solvers.
174175
The {attr}`grind hom` attribute can be added to the following kinds of theorems:
176+
175177
* To translate {lean}`f` into {lean}`g`, it should be the case that {lean}`(f x y).toU = g x.toU y.toU`.
176178
* Ordering relations can be translated by showing that {lean}`x ≤ y ↔ x.toU ≤ y.toU` and {lean}`x < y ↔ x.toU < y.toU`.
177179
* Numeric literals can be translated by providing a theorem that translates them into a function into {lean}`U`.
178180
This is done by adding the {attr}`grind hom` attribute to a theorem of the form {lean}`(OfNat.ofNat n : T).toU = h n`.
179181
* Conditionals can be translated by providing a theorem that shows that {lean}`(if p then x else y).toU = if p then x.toU else y.toU`.
180182

183+
As a last resort, fallback rules can be provided that are applied after other rules have failed to rewrite a term.
184+
These are typically used for rules that would otherwise overlap others.
185+
:::
186+
181187
Additional facts about the range of the mapping can be provided by tagging lemmas with {attr}`grind hom_pred`.
182188
This is typically used to restrict the range, such as by asserting that the target of {name}`Fin.val` is less than the {name}`Fin`'s bound.
183189
These lemmas are instantiated when the constants that they mention are used in terms that are not themselves rewritten by {attr}`grind hom` rules.
@@ -189,6 +195,30 @@ Because the mapping is injective, _disequality_ of terms in the new type implies
189195
Because they run only very early in the process, homomorphism lemmas are applied without a discharger.
190196
This means that they do not permit conditional rewrites that require further proving (though rewrites can still be made conditional on an instance-implicit hypothesis, and propositional hypotheses are permitted when they are fully determined by the left-hand side).
191197

198+
Homomorphism rules are declared using three attributes: {attr}`grind hom`, {attr}`grind hom fallback`, and {attr}`grind hom_pred`.
199+
These attributes respectively register homomorphism rules, fallback rules to be tried when the other {attr}`grind hom` rules don't apply, and facts about the range of the mapping.
200+
201+
:::syntax attr (title := "Homomorphism Rules")
202+
```grammar
203+
grind hom
204+
```
205+
{includeDocstring Lean.Parser.Attr.grindHom}
206+
:::
207+
208+
:::syntax attr (title := "Fallback Homomorphism Rules")
209+
```grammar
210+
grind hom fallback
211+
```
212+
{includeDocstring Lean.Parser.Attr.grindHomFallback}
213+
:::
214+
215+
:::syntax attr (title := "Homomorphism Predicates")
216+
```grammar
217+
grind hom_pred
218+
```
219+
{includeDocstring Lean.Parser.Attr.grindHomPred}
220+
:::
221+
192222

193223
When debugging, homomorphism rewrites can be observed by setting {option}`trace.grind.hom` or {option}`trace.grind.hom.pred` to `true`.
194224

@@ -531,3 +561,154 @@ example (h : a ++ b = .nil) : a = .nil := by
531561
grind [List.append_eq_nil_iff]
532562
```
533563
:::
564+
565+
:::example "Times of Day"
566+
567+
A time can be represented by hours and minutes:
568+
569+
```lean
570+
structure Time where
571+
hour : Fin 24
572+
minute : Fin 60
573+
```
574+
575+
```lean
576+
namespace Time
577+
```
578+
579+
Each time can be represented as the number of minutes since midnight.
580+
This representation is not unique, because more than a day's worth of minutes wrap around to a time in the next day.
581+
582+
```lean
583+
def toMinutes (t : Time) : Nat :=
584+
t.hour.val * 60 + t.minute.val
585+
586+
def ofMinutes (n : Nat) : Time where
587+
hour := ⟨n / 60 % 24, by grind⟩
588+
minute := ⟨n % 60, by grind⟩
589+
```
590+
591+
While it's not particularly sensible to add two points in time, it's perfectly reasonable to add minutes to a time, yielding a later time.
592+
```lean
593+
def addMinutes (t : Time) (n : Nat) : Time :=
594+
ofMinutes (t.toMinutes + n)
595+
```
596+
597+
One time is less than another if it occurs earlier in the day.
598+
```lean
599+
instance : LT Time where
600+
lt a b := a.toMinutes < b.toMinutes
601+
```
602+
603+
Using {attr}`grind hom`, these operators can be mapped directly to the natural numbers.
604+
605+
```lean
606+
@[grind hom]
607+
theorem eq_iff_toMinutes_eq (a b : Time) :
608+
a = b ↔ a.toMinutes = b.toMinutes := by
609+
constructor
610+
· intro h; rw [h]
611+
· obtain ⟨⟨h₁, _⟩, ⟨m₁, _⟩⟩ := a
612+
obtain ⟨⟨h₂, _⟩, ⟨m₂, _⟩⟩ := b
613+
simp only [toMinutes, mk.injEq, Fin.mk.injEq]
614+
grind
615+
616+
@[grind hom]
617+
theorem lt_iff_toMinutes_lt (a b : Time) :
618+
a < b ↔ a.toMinutes < b.toMinutes := by
619+
rfl
620+
621+
@[grind hom]
622+
theorem toMinutes_addMinutes (t : Time) (n : Nat) :
623+
(t.addMinutes n).toMinutes =
624+
(t.toMinutes + n) % 1440 := by
625+
have := t.hour.isLt
626+
have := t.minute.isLt
627+
simp only [addMinutes, ofMinutes, toMinutes]
628+
grind
629+
```
630+
631+
However, there is no rule that relates facts about the fields of {name}`Time` to minute counts.
632+
This leads to failures when using {tactic}`grind` to reason about the fields:
633+
634+
```lean +error
635+
example (a b : Time) (h : a.hour < b.hour) : a < b := by
636+
grind
637+
```
638+
639+
One way around this is to use the defining equation of {name}`toMinutes` as a {attr}`grind hom` rule.
640+
Using this rule causes _all_ applications of {name}`toMinutes` to be rewritten in terms of the time's field values.
641+
642+
```lean
643+
theorem toMinutes_eq (t : Time) :
644+
t.toMinutes = t.hour.val * 60 + t.minute.val := by
645+
rfl
646+
647+
attribute [local grind hom] toMinutes_eq in
648+
example (a b : Time) (h : a.hour < b.hour) : a < b := by
649+
grind
650+
```
651+
652+
Unfortunately, this rule is applied in situations where one of the more specific rules would have been better.
653+
It's a useful fallback, but it is not the best choice when the other rules could have been used.
654+
In particular, it takes precedence over {name}`toMinutes_addMinutes`, which causes this proof to fail:
655+
656+
```lean +error (name := timeOrdinary)
657+
attribute [local grind hom] toMinutes_eq in
658+
set_option trace.grind.hom true in
659+
example (t : Time) (h : t.hour < 23) :
660+
t < t.addMinutes 60 := by
661+
grind
662+
```
663+
```leanOutput timeOrdinary
664+
[grind.hom.pred] ↑t.hour < 24
665+
[grind.hom] t.hour < 23
666+
===>
667+
↑t.hour ≤ 22
668+
[grind.hom.pred] ↑t.minute < 60
669+
[grind.hom.pred] ↑(t.addMinutes 60).hour < 24
670+
[grind.hom.pred] ↑(t.addMinutes 60).minute < 60
671+
[grind.hom] t < t.addMinutes 60
672+
===>
673+
60 * ↑t.hour + ↑t.minute + 1 ≤ 60 * ↑(t.addMinutes 60).hour + ↑(t.addMinutes 60).minute
674+
```
675+
676+
The solution is to add {name}`toMinutes_eq` as a fallback rule:
677+
678+
```lean
679+
attribute [grind hom fallback] toMinutes_eq
680+
```
681+
682+
```lean (name := timeFallback)
683+
set_option trace.grind.hom true in
684+
example (t : Time) (h : t.hour < 23) :
685+
t < t.addMinutes 60 := by
686+
grind
687+
```
688+
```leanOutput timeFallback
689+
[grind.hom.pred] ↑t.hour < 24
690+
[grind.hom] t.hour < 23
691+
===>
692+
↑t.hour ≤ 22
693+
[grind.hom.pred] ↑t.minute < 60
694+
[grind.hom] t < t.addMinutes 60
695+
===>
696+
60 * ↑t.hour + ↑t.minute + 1 ≤ (60 * ↑t.hour + ↑t.minute + 60) % 1440
697+
```
698+
699+
With the fallback in place, all of these proofs succeed:
700+
701+
```lean
702+
example (t : Time) : t.addMinutes 1440 = t := by
703+
grind
704+
705+
example (t : Time) (m n : Nat) :
706+
(t.addMinutes m).addMinutes n =
707+
t.addMinutes (m + n) := by
708+
grind
709+
710+
example (t : Time) (h : t.hour = 23) (h' : t.minute = 59) :
711+
t.addMinutes 1 = ⟨0, 0⟩ := by
712+
grind
713+
```
714+
:::

‎lake-manifest.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "git",
66
"subDir": null,
77
"scope": "",
8-
"rev": "9c6a7446456e286dd0c18b62c28820c3236e7689",
8+
"rev": "022f91cf1ddf726357415909897276010cc48d01",
99
"name": "verso",
1010
"manifestFile": "lake-manifest.json",
1111
"inputRev": "nightly-testing",
@@ -45,7 +45,7 @@
4545
"type": "git",
4646
"subDir": null,
4747
"scope": "",
48-
"rev": "fb13df72ecefd8ddbf9291021d7f33a8673eb57b",
48+
"rev": "b7fb2b866d25bd937a2069931f9733197521cb27",
4949
"name": "plausible",
5050
"manifestFile": "lake-manifest.json",
5151
"inputRev": "main",

‎lean-toolchain‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
leanprover/lean4:nightly-2026-09-27
1+
leanprover/lean4:nightly-2026-09-29

0 commit comments

Comments
 (0)