Skip to content

Commit b6fc2c9

Browse files
chore: bump to nightly-2026-08-18 (#925)
1 parent df277a7 commit b6fc2c9

17 files changed

Lines changed: 1014 additions & 82 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ functors
8484
guillemet
8585
guillemets
8686
hoc
87+
idempotent
88+
idempotence
8789
impredicative
8890
impredicativity
8991
indexable

Main.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ where
5656
(`Verso.Genre.Manual.doc.tactic.conv, 60),
5757
(`Verso.Genre.Manual.doc.option, 60),
5858
(`Verso.Genre.Manual.example, 60),
59+
(`Manual.examples, 60),
5960
]
6061
},
6162
}

Manual/BasicTypes/Option.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ In {lean}`getAlpha`, a line of input is read.
127127
If the line consists only of letters (after removing whitespace from the beginning and end of it), then it is returned; otherwise, the function returns {name}`none`.
128128

129129
```lean
130-
def getAlpha : IO (Option String) := do
131-
let line := (← (← IO.getStdin).getLine).trim
132-
if line.length > 0 && line.all Char.isAlpha then
130+
def getAlpha : IO (Option String.Slice) := do
131+
let line := (← (← IO.getStdin).getLine).trimAscii
132+
if !line.isEmpty && line.all Char.isAlpha then
133133
return line
134134
else
135135
return none

Manual/Grind.lean

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import Manual.Grind.Cutsat
1919
import Manual.Grind.Algebra
2020
import Manual.Grind.Linarith
2121
import Manual.Grind.Annotation
22+
import Manual.Grind.Hom
23+
import Manual.Grind.AC
2224
import Manual.Grind.ExtendedExamples
2325

2426
-- Needed for the if-then-else normalization example.
@@ -168,6 +170,48 @@ These theorems will typically include a symbol prefix such as `=`, `←`, or `
168170
pattern that triggered the instantiation. See the {ref "e-matching"}[section on E-matching] for details.
169171
Some theorems may be labelled with a `usr` prefix, which indicates that a custom pattern was used.
170172

173+
# Local Definitions
174+
175+
{tactic}`grind` has two flags that control how it treats local definitions with {keywordOf Lean.Parser.Term.let}`let`.
176+
They are enabled by default, but duplicating the local definition's value can lead to the term exploding in size; consider disabling them when working with terms that contain many nested {keywordOf Lean.Parser.Term.let}`let`s.
177+
178+
: `zeta` (default `true`)
179+
180+
This controls whether {tactic}`grind` performs {tech (key := "ζ")}[ζ-reduction].
181+
Unless this flag is disabled, terms that contain a {keywordOf Lean.Parser.Term.let}`let` are reduced before they are added to the whiteboard, so that {lean}`let x := 5; x + x` is reduced to {lean}`5 + 5` before further processing.
182+
If it is disabled, the term is not reduced.
183+
```lean -show
184+
example : (let x := 5; x + x) = 10 := by grind
185+
/-- `grind` failed -/
186+
#guard_msgs (substring := true) in
187+
example : (let x := 5; x + x) = 10 := by grind -zeta
188+
```
189+
190+
: `zetaDelta` (default `true`)
191+
192+
This flag controls whether {tactic}`grind` replaces variables in terms that have local definitions in the context with their definitions.
193+
:::tacticExample
194+
If a proof goal is {goal}`let x := 5; (x + x = 10)`, running
195+
```setup
196+
intro x
197+
```
198+
results in a proof state in which the definition of `x` is in the context:
199+
```pre
200+
x : Nat := 5
201+
⊢ x + x = 10
202+
```
203+
Running {tacticStep}`grind` succeeds, because {lean}`5` is substituted for `x`.
204+
Without the `zetaDelta` flag, it fails.
205+
```post -show
206+
```
207+
:::
208+
```lean -show
209+
example : let x := 5; (x + x = 10) := by intro x; grind
210+
/-- `grind` failed -/
211+
#guard_msgs (substring := true) in
212+
example : let x := 5; (x + x = 10) := by intro x; grind -zetaDelta
213+
```
214+
171215
{include 1 Manual.Grind.CongrClosure}
172216

173217
{include 1 Manual.Grind.ConstraintPropagation}
@@ -176,6 +220,8 @@ Some theorems may be labelled with a `usr` prefix, which indicates that a custom
176220

177221
{include 1 Manual.Grind.EMatching}
178222

223+
{include 1 Manual.Grind.AC}
224+
179225
{include 1 Manual.Grind.Cutsat}
180226

181227
{include 1 Manual.Grind.Algebra}
@@ -301,4 +347,6 @@ Threshold notices, learned equivalence classes, integer assignments, algebraic b
301347
TBD
302348
```
303349

350+
{include 1 Manual.Grind.Hom}
351+
304352
{include 1 Manual.Grind.ExtendedExamples}

Manual/Grind/AC.lean

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Author: David Thrane Christiansen
5+
-/
6+
7+
import VersoManual
8+
9+
import Lean.Parser.Term
10+
11+
import Manual.Meta
12+
import Manual.Papers
13+
14+
15+
open Verso.Genre Manual
16+
open Verso.Genre.Manual.InlineLean
17+
open Verso.Doc.Elab (CodeBlockExpander)
18+
open Verso.Code.External (lit)
19+
20+
open Lean.Elab.Tactic.GuardMsgs.WhitespaceMode
21+
22+
#doc (Manual) "Associativity and Commutativity" =>
23+
%%%
24+
tag := "grind-ac"
25+
%%%
26+
27+
When an operator is associative, {tactic}`grind` can efficiently rewrite terms that contain the operator to a normal form, making it easier to prove equalities that involve the operator.
28+
This rewriting can make use of further facts about the operator, such as the fact that it is commutative or idempotent, or that certain values are identities.
29+
Rewriting occurs both when adding terms to the “whiteboard” and in response to facts added by other solvers.
30+
This solver is controlled using the `ac` flag, and it is on by default.
31+
The `acSteps` option, which defaults to `1000`, controls how many `ac` rewriting steps are allowed.
32+
33+
:::example "Strings"
34+
The `ac` solver can reason about strings.
35+
It's common for string-processing code to compute a prefix, and then later append further values.
36+
Using `ac`, these can be reasoned about more conveniently:
37+
```lean
38+
example (dir file p) (h : dir ++ "/" = p) :
39+
dir ++ ("/" ++ file) = p ++ file := by grind
40+
```
41+
42+
```lean -show +error
43+
-- To make sure it's AC solving this
44+
example (dir file p) (h : dir ++ "/" = p) :
45+
dir ++ ("/" ++ file) = p ++ file := by grind -ac
46+
```
47+
:::
48+
49+
# Normal Forms
50+
51+
:::leanSection
52+
```lean -show
53+
variable {α : Type u} {op : α → α → α} [Std.Associative op] {u x y z : α}
54+
```
55+
56+
The normal forms used by the `ac` solver depend on the properties that have been registered for the operator in question.
57+
58+
: Associativity
59+
60+
Associative operators are normalized by reducing nested trees of the operator to lists of operands.
61+
If {lean}`op` is associative, then the normal form of both {lean}`op (op x y) z` and {lean}`op x (op y z)` is {lean}`[x, y, z]`.
62+
63+
: Commutativity
64+
65+
Operators that are associative and commutative are normalized by sorting the operand lists.
66+
If {lean}`op` is associative and commutative, then the normal form of both {lean}`op z (op x y)` and {lean}`op (op x z) y` is {lean}`[x, y, z]`.
67+
In other words, the normal form is a multiset.
68+
69+
: Idempotence
70+
71+
If the operator is idempotent, then runs of duplicate elements are collapsed in the operand list.
72+
If {lean}`op` is associative and idempotent, then the normal form of {lean}`op x (op x y)` is {lean}`[x, y]`, but the normal form of {lean}`op (op x y) x` is still {lean}`[x, y, x]`.
73+
This collapsing occurs after sorting the list if the operator is commutative, so if {lean}`op` is associative, commutative, and idempotent, then the normal form of {lean}`op (op x y) x` is also {lean}`[x, y]`.
74+
In other words, the normal form of an associative, commutative, and idempotent operator is a set.
75+
76+
: Identities
77+
78+
If the operator has a unit, then it is removed from the normal form.
79+
That is, if {lean}`u` is a unit of the associative operator {lean}`op`, then the normal form of {lean}`op x (op u y)` is {lean}`[x, y]`.
80+
81+
:::
82+
83+
84+
# Extension
85+
86+
The `ac` solver can be extended to support new operators by providing an instance of {name}`Std.Associative` for the operator.
87+
Instances of {name}`Std.Commutative`, {name}`Std.IdempotentOp`, and {name}`Std.LawfulIdentity` further extend its capabilities by identifying more terms.
88+
89+
{docstring Std.Associative}
90+
91+
{docstring Std.Commutative}
92+
93+
{docstring Std.IdempotentOp}
94+
95+
{docstring Std.LawfulIdentity}
96+
97+
While extending the {lit}`ac` solver, it can be useful to observe its operation.
98+
The option {option}`trace.grind.ac`, as well as the more specific options {option}`trace.grind.ac.assert`, {option}`trace.grind.ac.internalize`, and {option}`trace.grind.ac.basis`, can be used to observe its internal behavior.
99+
In particular, {option}`trace.grind.ac.assert` displays the results of normalization that are added to the “whiteboard.”
100+
101+
{optionDocs trace.grind.ac}
102+
103+
{optionDocs trace.grind.ac.assert}
104+
105+
{optionDocs trace.grind.ac.internalize}
106+
107+
{optionDocs trace.grind.ac.basis}
108+
109+
:::example "Idempotence and Identity"
110+
111+
{name}`Flags` tracks a set of Boolean flags, each of which corresponds to a bit position.
112+
Each flag is either set or clear, and all flags are clear when the bit value is {lean}`0`.
113+
The union of two sets of flags is found by taking the bit-wise OR of their bit representations.
114+
```lean
115+
structure Flags where
116+
bits : Nat
117+
118+
namespace Flags
119+
120+
def union (a b : Flags) : Flags := ⟨a.bits ||| b.bits⟩
121+
def none : Flags := ⟨0
122+
123+
```
124+
The union operation on flags is associative, commutative, and idempotent, and {name}`Flags.none` is an identity:
125+
```lean
126+
instance : Std.Associative union where
127+
assoc x y z := by simp only [union, mk.injEq]; grind
128+
instance : Std.Commutative union where
129+
comm x y := by simp only [union, mk.injEq]; grind
130+
instance : Std.IdempotentOp union where
131+
idempotent x := by simp [union]
132+
instance : Std.LawfulIdentity union none where
133+
left_id a := by simp [union, none]
134+
right_id a := by simp [union, none]
135+
```
136+
With these instances, the `ac` solver can dispatch all of the following goals.
137+
In the second example, idempotency can only be used due to commutativity, because the two identical arguments are not adjacent in the original term.
138+
```lean
139+
example : union a (union b c) = union (union c a) b := by grind
140+
example : union (union a b) a = union a b := by grind
141+
example : union a none = a := by grind
142+
example :
143+
union (union a b) (union none (union b c))
144+
= union a (union b c) := by
145+
grind
146+
```
147+
The negated normal-form equality that is added to the context can be seen using {option}`trace.grind.ac.assert`:
148+
```lean (name := traceAc)
149+
set_option trace.grind.ac.assert true in
150+
example : union (union a b) a = union a (union b none) := by grind
151+
```
152+
```leanOutput traceAc
153+
[grind.ac.assert] a.union b ≠ a.union b
154+
```
155+
156+
```lean -show
157+
-- check solver
158+
/-- error: `grind` failed -/
159+
#guard_msgs (substring := true) in
160+
example : union a (union b c) = union (union c a) b := by grind -ac
161+
162+
/-- error: `grind` failed -/
163+
#guard_msgs (substring := true) in
164+
example : union (union a b) a = union a b := by grind -ac
165+
166+
/-- error: `grind` failed -/
167+
#guard_msgs (substring := true) in
168+
example : union a none = a := by grind -ac
169+
170+
/-- error: `grind` failed -/
171+
#guard_msgs (substring := true) in
172+
example :
173+
union (union a b) (union none (union b c))
174+
= union a (union b c) := by grind -ac
175+
176+
```
177+
178+
:::
179+
180+
::::example "Difference Lists" (tag := "difference-lists-ac")
181+
182+
A {deftech}_difference list_ is a clever representation of lists as functions that avoids the quadratic overhead of appending to the end of lists.
183+
Because Lean supports {ref "Array"}[efficient arrays], they are typically not useful in day-to-day code, but they effectively demonstrate how to extend the `ac` solver.
184+
185+
:::leanSection
186+
```lean -show
187+
variable {xs ys : List α}
188+
```
189+
A difference list is a function from lists to lists.
190+
The list {lean}`xs` is represented by a function that, when applied to {lean}`ys`, returns {lean}`xs ++ ys`.
191+
:::
192+
193+
```lean
194+
def DList α := List α → List α
195+
```
196+
197+
The empty list is the identity function.
198+
An item is added to the beginning of a list by creating a function that adds the element.
199+
Appending two lists is function composition.
200+
```lean
201+
def DList.empty : DList α := id
202+
203+
def DList.cons (x : α) (xs : DList α) : DList α :=
204+
fun ys => (x :: xs ys)
205+
206+
instance : Append (DList α) where
207+
append xs ys := xs ∘ ys
208+
```
209+
210+
The proofs that appending two difference lists is associative and that the empty difference list is a left and right identity of the append operator succeed by reflexivity because definitional equality includes the β and η laws for functions:
211+
```lean
212+
instance : Std.Associative (α := DList α) (· ++ ·) where
213+
assoc xs ys zs := by rfl
214+
215+
instance : Std.LawfulIdentity (α := DList α) (· ++ ·) .empty where
216+
left_id xs := by rfl
217+
right_id xs := by rfl
218+
```
219+
220+
Given these instances, {tactic}`grind` can dispatch many proofs.
221+
```lean
222+
variable (a b c d x y : DList Nat)
223+
```
224+
225+
Because append is associative, terms can be rebracketed and units vanish:
226+
```lean
227+
example : ((a ++ b) ++ c) ++ d = a ++ (b ++ (c ++ d)) := by grind
228+
229+
example : (a ++ .empty) ++ (.empty ++ b) = a ++ b := by grind
230+
231+
example : (.empty ++ .empty : DList Nat) = .empty := by grind
232+
```
233+
234+
While {ref "simp-rewrites"}[simplification lemmas] could easily solve those problems, {tactic}`grind`'s `ac` solver allows a greater degree of flexibility.
235+
To use the hypotheses in these examples, it would not be sufficient to just rewrite goals and hypotheses to a normal form that reassociated an associative operator to the right:
236+
```lean
237+
example (h : a ++ b = x) : a ++ (b ++ c) = x ++ c := by grind
238+
239+
example (h : a ++ b = x) :
240+
(a ++ .empty) ++ (b ++ c) = x ++ c := by grind
241+
242+
example (h₁ : a ++ b = x) (h₂ : x ++ c = y) :
243+
a ++ (b ++ c) = y := by grind
244+
```
245+
Because appending difference lists is not commutative, order still matters:
246+
```lean +error (name := grindErr)
247+
example : a ++ b = b ++ a := by grind
248+
```
249+
```leanOutput grindErr
250+
`grind` failed
251+
case grind
252+
a b c d x y : DList Nat
253+
h : ¬a ++ b = b ++ a
254+
⊢ False
255+
[grind] Goal diagnostics
256+
[facts] Asserted facts
257+
[eqc] False propositions
258+
[assoc] Operator `HAppend.hAppend`
259+
```
260+
::::
261+
262+
# Exclusions
263+
264+
The `ac` solver does not apply to some built-in operators, namely {name}`And`, {name}`Or`, and {name}`Iff`.
265+
They are better served by other {tactic}`grind` features.
266+
When the operand type has {name}`Lean.Grind.CommRing` and/or {name}`Lean.Grind.CommSemiring` instances, `ac` omits its rules for certain operators that are usually better solved by {ref "grind-ring"}[the `ring` solver], namely `+`, `-`, `*`, `/`, and `^`.
267+
268+
```lean -show
269+
-- `And` and `Or` have `Std.Associative` instances, so the exclusion is
270+
-- what stops the `ac` solver from claiming them.
271+
#guard_msgs in
272+
set_option trace.grind.debug.ac.op true in
273+
example (a b c : Prop) : (a ∧ b) ∧ c ↔ a ∧ (b ∧ c) := by grind
274+
275+
#guard_msgs in
276+
set_option trace.grind.debug.ac.op true in
277+
example (a b c : Prop) : (a ∨ b) ∨ c ↔ a ∨ (b ∨ c) := by grind
278+
279+
-- Likewise `+` and `*`, which are left to the arithmetic solvers.
280+
#guard_msgs in
281+
set_option trace.grind.debug.ac.op true in
282+
example (a b c : Nat) : a + (b + c) = (c + a) + b := by grind
283+
284+
#guard_msgs in
285+
set_option trace.grind.debug.ac.op true in
286+
example (a b c : Nat) : a * (b * c) = (c * a) * b := by grind
287+
```

0 commit comments

Comments
 (0)