Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.

Commit a027a37

Browse files
committed
feat(tactic/simps): user-provided names for projections (#4663)
Adds the functionality to specify custom projection names, like this: ```lean initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply) ``` These names will always be used and cannot (yet) be manually overridden. Implement this for embeddings: `initialize_simps_projections embedding (to_fun → apply)`. Rename `fixed_points.to_alg_hom_apply -> fixed_points.to_alg_hom_apply_apply`, since `@[simps]` now generates the name `to_alg_hom_apply` instead of `to_alg_hom_to_fun`.
1 parent e0b153b commit a027a37

File tree

6 files changed

+161
-40
lines changed

6 files changed

+161
-40
lines changed

src/algebra/big_operators/basic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ calc (∏ x in s.sigma t, f x) =
273273
∏ x in s.bind (λa, (t a).map (function.embedding.sigma_mk a)), f x : by rw sigma_eq_bind
274274
... = ∏ a in s, ∏ x in (t a).map (function.embedding.sigma_mk a), f x :
275275
prod_bind $ assume a₁ ha a₂ ha₂ h x hx,
276-
by { simp only [inf_eq_inter, mem_inter, mem_map, function.embedding.sigma_mk_to_fun] at hx,
276+
by { simp only [inf_eq_inter, mem_inter, mem_map, function.embedding.sigma_mk_apply] at hx,
277277
rcases hx with ⟨⟨y, hy, rfl⟩, ⟨z, hz, hz'⟩⟩, cc }
278278
... = ∏ a in s, ∏ s in t a, f ⟨a, s⟩ :
279279
prod_congr rfl $ λ _ _, prod_map _ _ _

src/field_theory/fixed.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,14 @@ fintype_card_le_findim_of_linear_independent $ linear_independent_to_linear_map
239239

240240
namespace fixed_points
241241
/-- Embedding produced from a faithful action. -/
242-
@[simps to_fun {fully_applied := ff}]
242+
@[simps apply {fully_applied := ff}]
243243
def to_alg_hom (G : Type u) (F : Type v) [group G] [field F]
244244
[faithful_mul_semiring_action G F] : G ↪ (F →ₐ[fixed_points G F] F) :=
245245
{ to_fun := λ g, { commutes' := λ x, x.2 g,
246246
.. mul_semiring_action.to_semiring_hom G F g },
247247
inj' := λ g₁ g₂ hg, injective_to_semiring_hom G F $ ring_hom.ext $ λ x, alg_hom.ext_iff.1 hg x, }
248248

249-
lemma to_alg_hom_apply {G : Type u} {F : Type v} [group G] [field F]
249+
lemma to_alg_hom_apply_apply {G : Type u} {F : Type v} [group G] [field F]
250250
[faithful_mul_semiring_action G F] (g : G) (x : F) :
251251
to_alg_hom G F g x = g • x :=
252252
rfl

src/logic/embedding.lean

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ infixr ` ↪ `:25 := embedding
2424

2525
instance {α : Sort u} {β : Sort v} : has_coe_to_fun (α ↪ β) := ⟨_, embedding.to_fun⟩
2626

27+
initialize_simps_projections embedding (to_fun → apply)
28+
2729
end function
2830

2931
/-- Convert an `α ≃ β` to `α ↪ β`. -/
@@ -164,12 +166,12 @@ section sigma
164166
variables {α α' : Type*} {β : α → Type*} {β' : α' → Type*}
165167

166168
/-- `sigma.mk` as an `function.embedding`. -/
167-
@[simps to_fun] def sigma_mk (a : α) : β a ↪ Σ x, β x :=
169+
@[simps apply] def sigma_mk (a : α) : β a ↪ Σ x, β x :=
168170
⟨sigma.mk a, sigma_mk_injective⟩
169171

170172
/-- If `f : α ↪ α'` is an embedding and `g : Π a, β α ↪ β' (f α)` is a family
171173
of embeddings, then `sigma.map f g` is an embedding. -/
172-
@[simps to_fun] def sigma_map (f : α ↪ α') (g : Π a, β a ↪ β' (f a)) :
174+
@[simps apply] def sigma_map (f : α ↪ α') (g : Π a, β a ↪ β' (f a)) :
173175
(Σ a, β a) ↪ Σ a', β' a' :=
174176
⟨sigma.map f (λ a, g a), f.injective.sigma_map (λ a, (g a).injective)⟩
175177

@@ -199,7 +201,7 @@ protected def subtype_map {α β} {p : α → Prop} {q : β → Prop} (f : α
199201
open set
200202

201203
/-- `set.image` as an embedding `set α ↪ set β`. -/
202-
@[simps to_fun] protected def image {α β} (f : α ↪ β) : set α ↪ set β :=
204+
@[simps apply] protected def image {α β} (f : α ↪ β) : set α ↪ set β :=
203205
⟨image f, f.2.image_injective⟩
204206

205207
end embedding
@@ -219,7 +221,7 @@ end equiv
219221
namespace set
220222

221223
/-- The injection map is an embedding between subsets. -/
222-
@[simps to_fun] def embedding_of_subset {α} (s t : set α) (h : s ⊆ t) : s ↪ t :=
224+
@[simps apply] def embedding_of_subset {α} (s t : set α) (h : s ⊆ t) : s ↪ t :=
223225
⟨λ x, ⟨x.1, h x.2⟩, λ ⟨x, hx⟩ ⟨y, hy⟩ h, by { congr, injection h }⟩
224226

225227
end set

src/measure_theory/haar_measure.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ begin
208208
{ simp only [mem_set_of_eq], refine subset.trans (image_subset _ h1s) _,
209209
rintro _ ⟨g₁, ⟨_, ⟨g₂, rfl⟩, ⟨_, ⟨hg₂, rfl⟩, hg₁⟩⟩, rfl⟩,
210210
simp only [mem_preimage] at hg₁, simp only [exists_prop, mem_Union, finset.mem_map,
211-
equiv.coe_mul_right, exists_exists_and_eq_and, mem_preimage, equiv.to_embedding_to_fun],
211+
equiv.coe_mul_right, exists_exists_and_eq_and, mem_preimage, equiv.to_embedding_apply],
212212
refine ⟨_, hg₂, _⟩, simp only [mul_assoc, hg₁, inv_mul_cancel_left] }
213213
end
214214

src/tactic/simps.lean

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ used by the `@[simps]` attribute.
4545
- To change the default value, see Note [custom simps projection].
4646
- You are strongly discouraged to add this attribute manually.
4747
- The first argument is the list of names of the universe variables used in the structure
48-
- The second argument is the expressions that correspond to the projections of the structure
49-
(these can contain the universe parameters specified in the first argument).
48+
- The second argument is a list that consists of
49+
- a custom name for each projection of the structure
50+
- an expressions for each projections of the structure (definitionally equal to the
51+
corresponding projection). These expressions can contain the universe parameters specified
52+
in the first argument).
5053
-/
51-
@[user_attribute] meta def simps_str_attr : user_attribute unit (list name × list expr) :=
54+
@[user_attribute] meta def simps_str_attr : user_attribute unit (list name × list (name × expr)) :=
5255
{ name := `_simps_str,
5356
descr := "An attribute specifying the projection of the given structure.",
5457
parser := do e ← texpr, eval_pexpr _ e }
@@ -77,6 +80,11 @@ attribute [notation_class* coe_fn] has_coe_to_fun
7780
Get the projections used by `simps` associated to a given structure `str`. The second component is
7881
the list of projections, and the first component the (shared) list of universe levels used by the
7982
projections.
83+
84+
The returned information is also stored in a parameter of the attribute `@[_simps_str]`, which
85+
is given to `str`. If `str` already has this attribute, the information is read from this
86+
attribute instead.
87+
8088
The returned universe levels are the universe levels of the structure. For the projections there
8189
are three cases
8290
* If the declaration `{structure_name}.simps.{projection_name}` has been declared, then the value
@@ -102,15 +110,31 @@ attribute [notation_class* coe_fn] has_coe_to_fun
102110
```
103111
def equiv.simps.inv_fun {α β} (e : α ≃ β) : β → α := e.symm
104112
```
113+
114+
Optionally, this command accepts two optional arguments
115+
* If `trace_if_exists` the command will always generate a trace message when the structure already
116+
has the attribute `@[_simps_str]`.
117+
* The `name_changes` argument accepts a list of pairs `(old_name, new_name)`. This is used to
118+
change the projection name `old_name` to the custom projection name `new_name`. Example:
119+
for the structure `equiv` the projection `to_fun` could be renamed `apply`. This name will be
120+
used for parsing and generating projection names. This argument is ignored if the structure
121+
already has an existing attribute.
105122
-/
106123
-- if performance becomes a problem, possible heuristic: use the names of the projections to
107124
-- skip all classes that don't have the corresponding field.
108-
meta def simps_get_raw_projections (e : environment) (str : name) :
109-
tactic (list name × list expr) := do
125+
meta def simps_get_raw_projections (e : environment) (str : name) (trace_if_exists : bool := ff)
126+
(name_changes : list (name × name) := []) : tactic (list name × list (name × expr)) := do
110127
has_attr ← has_attribute' `_simps_str str,
111128
if has_attr then do
112-
when_tracing `simps.verbose trace!"[simps] > found projection information for structure {str}",
113-
simps_str_attr.get_param str
129+
data ← simps_str_attr.get_param str,
130+
to_print ← data.2.mmap $ λ s, to_string <$> pformat!"Projection {s.1}: {s.2}",
131+
let to_print := string.join $ to_print.intersperse "\n > ",
132+
-- We always trace this when called by `initialize_simps_projections`,
133+
-- because this doesn't do anything extra (so should not occur in mathlib).
134+
-- It can be useful to find the projection names.
135+
when (is_trace_enabled_for `simps.verbose || trace_if_exists) trace!
136+
"[simps] > Already found projection information for structure {str}:\n > {to_print}",
137+
return data
114138
else do
115139
when_tracing `simps.verbose trace!
116140
"[simps] > generating projection information for structure {str}:",
@@ -172,10 +196,17 @@ Expected type:\n {raw_expr_type}" },
172196
when_tracing `simps.verbose trace!
173197
" > using {proj_nm} instead of the default projection {relevant_proj.last}.",
174198
return $ raw_exprs.update_nth pos lambda_raw_expr) <|> return raw_exprs) raw_exprs,
175-
when_tracing `simps.verbose trace!
176-
"[simps] > generated projections for {str}:\n > {raw_exprs}",
177-
simps_str_attr.set str (raw_univs, raw_exprs) tt,
178-
return (raw_univs, raw_exprs)
199+
proj_names ← e.structure_fields str,
200+
-- if we find the name in name_changes, change the name
201+
let proj_names : list name := proj_names.map $
202+
λ nm, (name_changes.find $ λ p : _ × _, p.1 = nm).elim nm prod.snd,
203+
let projs := proj_names.zip raw_exprs,
204+
when_tracing `simps.verbose $ do {
205+
to_print ← projs.mmap $ λ s, to_string <$> pformat!"Projection {s.1}: {s.2}",
206+
let to_print := string.join $ to_print.intersperse "\n > ",
207+
trace!"[simps] > generated projections for {str}:\n > {to_print}" },
208+
simps_str_attr.set str (raw_univs, projs) tt,
209+
return (raw_univs, projs)
179210

180211
/--
181212
You can specify custom projections for the `@[simps]` attribute.
@@ -194,39 +225,65 @@ Expected type:\n {raw_expr_type}" },
194225
library_note "custom simps projection"
195226

196227
/-- Specify simps projections, see Note [custom simps projection].
197-
Set `trace.simps.verbose` to true to see the generated projections. -/
228+
You can specify custom names by writing e.g.
229+
`initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.
230+
Set `trace.simps.verbose` to true to see the generated projections.
231+
If the projections were already specified before, you can call `initialize_simps_projections`
232+
again to see the generated projections. -/
198233
@[user_command] meta def initialize_simps_projections_cmd
199234
(_ : parse $ tk "initialize_simps_projections") : parser unit := do
200235
env ← get_env,
201-
ns ← many ident,
202-
ns.mmap' $ λ nm, do nm ← resolve_constant nm, simps_get_raw_projections env nm
236+
ns ← (prod.mk <$> ident <*> (tk "(" >>
237+
sep_by (tk ",") (prod.mk <$> ident <*> (tk "->" >> ident)) <* tk ")")?)*,
238+
ns.mmap' $ λ data, do
239+
nm ← resolve_constant data.1,
240+
simps_get_raw_projections env nm tt $ data.2.get_or_else []
203241

204242
/--
205243
Get the projections of a structure used by `@[simps]` applied to the appropriate arguments.
206-
Returns a list of triples (projection expression, projection name, corresponding right-hand-side),
207-
one for each projection.
244+
Returns a list of quadruples
245+
(projection expression, given projection name, original (full) projection name,
246+
corresponding right-hand-side),
247+
one for each projection. The given projection name is the name for the projection used by the user
248+
used to generate (and parse) projection names. The original projection name is the actual
249+
projection name in the structure, which is only used to check whether the expression is an
250+
eta-expansion of some other expression. For example, in the structure
251+
252+
Example 1: ``simps_get_projection_exprs env `(α × β) `(⟨x, y⟩)`` will give the output
253+
```
254+
[(`(@prod.fst.{u v} α β), `fst, `prod.fst, `(x)),
255+
(`(@prod.snd.{u v} α β), `snd, `prod.snd, `(y))]
256+
```
208257
209-
Example: ``simps_get_projection_exprs env `(α × β) `(⟨x, y⟩)`` will give the output
258+
Example 2: ``simps_get_projection_exprs env `(α ≃ α) `(⟨id, id, λ _, rfl, λ _, rfl⟩)``
259+
will give the output
210260
```
211-
[(`(@prod.fst.{u v} α β), `prod.fst, `(x)), (`(@prod.snd.{u v} α β), `prod.snd, `(y))]
261+
[(`(@equiv.to_fun.{u u} α α), `apply, `equiv.to_fun, `(id)),
262+
(`(@equiv.inv_fun.{u u} α α), `symm_apply, `equiv.inv_fun, `(id)),
263+
...,
264+
...]
212265
```
266+
The last two fields of the list correspond to the propositional fields of the structure,
267+
and are rarely/never used.
213268
-/
214269
-- This function does not use `tactic.mk_app` or `tactic.mk_mapp`, because the the given arguments
215270
-- might not uniquely specify the universe levels yet.
216271
meta def simps_get_projection_exprs (e : environment) (tgt : expr)
217-
(rhs : expr) : tactic $ list $ expr × name × expr := do
272+
(rhs : expr) : tactic $ list $ expr × name × name × expr := do
218273
let params := get_app_args tgt, -- the parameters of the structure
219274
(params.zip $ (get_app_args rhs).take params.length).mmap' (λ ⟨a, b⟩, is_def_eq a b)
220275
<|> fail "unreachable code (1)",
221276
let str := tgt.get_app_fn.const_name,
222-
projs ← e.structure_fields_full str,
223277
let rhs_args := (get_app_args rhs).drop params.length, -- the fields of the object
224-
guard (rhs_args.length = projs.length) <|> fail "unreachable code (2)",
225-
(raw_univs, raw_exprs) ← simps_get_raw_projections e str,
278+
(raw_univs, projs_and_raw_exprs) ← simps_get_raw_projections e str,
279+
guard (rhs_args.length = projs_and_raw_exprs.length) <|> fail "unreachable code (2)",
226280
let univs := raw_univs.zip tgt.get_app_fn.univ_levels,
281+
let projs := projs_and_raw_exprs.map $ prod.fst,
282+
original_projection_names ← e.structure_fields_full str,
283+
let raw_exprs := projs_and_raw_exprs.map $ prod.snd,
227284
let proj_exprs := raw_exprs.map $
228285
λ raw_expr, (raw_expr.instantiate_univ_params univs).instantiate_lambdas_or_apps params,
229-
return $ proj_exprs.zip $ projs.zip rhs_args
286+
return $ proj_exprs.zip $ projs.zip $ original_projection_names.zip rhs_args
230287

231288
/--
232289
Configuration options for the `@[simps]` attribute.
@@ -316,8 +373,8 @@ meta def simps_add_projections : ∀(e : environment) (nm : name) (suffix : stri
316373
if is_constant_of (get_app_fn rhs_ap) intro then do -- if the value is a constructor application
317374
tuples ← simps_get_projection_exprs e tgt rhs_ap,
318375
let projs := tuples.map $ λ x, x.2.1,
319-
let pairs := tuples.map $ λ x, x.2,
320-
eta ← expr.is_eta_expansion_aux rhs_ap pairs, -- check whether `rhs_ap` is an eta-expansion
376+
let pairs := tuples.map $ λ x, x.2.2,
377+
eta ← rhs_ap.is_eta_expansion_aux pairs, -- check whether `rhs_ap` is an eta-expansion
321378
let rhs_ap := eta.lhoare rhs_ap, -- eta-reduce `rhs_ap`
322379
/- As a special case, we want to automatically generate the current projection if `rhs_ap`
323380
was an eta-expansion. Also, when this was a desired projection, we need to generate the
@@ -335,8 +392,14 @@ meta def simps_add_projections : ∀(e : environment) (nm : name) (suffix : stri
335392
let x := (todo.find $ λ x, projs.all $ λ proj, ¬ ("_" ++ proj.last).is_prefix_of x).iget,
336393
simp_lemma := nm.append_suffix $ suffix ++ x,
337394
needed_proj := (x.split_on '_').tail.head in
338-
fail!"Invalid simp-lemma {simp_lemma}. Projection {needed_proj} doesn't exist.",
339-
tuples.mmap' $ λ ⟨proj_expr, proj, new_rhs⟩, do
395+
fail!
396+
"Invalid simp-lemma {simp_lemma}. Structure {str} does not have projection {needed_proj}.
397+
The known projections are:
398+
{projs}
399+
You can also see this information by running
400+
`initialize_simps_projections {str}`.
401+
Note: the projection names used by @[simps] might not correspond to the projection names in the structure.",
402+
tuples.mmap' $ λ ⟨proj_expr, proj, _, new_rhs⟩, do
340403
new_type ← infer_type new_rhs,
341404
let new_todo := todo.filter_map $ λ x, string.get_rest x $ "_" ++ proj.last,
342405
b ← is_prop new_type,
@@ -380,7 +443,7 @@ meta def simps_tac (nm : name) (cfg : simps_cfg := {}) (todo : list string := []
380443

381444
/-- The parser for the `@[simps]` attribute. -/
382445
meta def simps_parser : parser (list string × simps_cfg) := do
383-
/- note: we currently don't check whether the user has written a nonsense namespace as arguments. -/
446+
/- note: we don't check whether the user has written a nonsense namespace in an argument. -/
384447
prod.mk <$> many (name.last <$> ident) <*>
385448
(do some e ← parser.pexpr? | return {}, eval_pexpr simps_cfg e)
386449

@@ -420,10 +483,14 @@ derives two simp-lemmas:
420483
⇑((e₁.trans e₂).symm) a = (⇑(e₁.symm) ∘ ⇑(e₂.symm)) a
421484
```
422485
486+
* You can specify custom projection names, by specifying the new projection names using
487+
`initialize_simps_projections`.
488+
Example: `initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.
489+
423490
* If one of the fields itself is a structure, this command will recursively create
424491
simp-lemmas for all fields in that structure.
425492
* Exception: by default it will not recursively create simp-lemmas for fields in the structures
426-
`prod` and `pprod`. Give explicit projection names to override this.
493+
`prod` and `pprod`. Give explicit projection names to override this behavior.
427494
428495
Example:
429496
```lean
@@ -432,7 +499,7 @@ derives two simp-lemmas:
432499
```
433500
generates
434501
```lean
435-
@[simp] lemma foo_fst : foo.fst = (1, 2)
502+
@[simp] lemma foo_fst : foo.fst = (1, 2)
436503
@[simp] lemma foo_snd_fst : foo.snd.fst = 3
437504
@[simp] lemma foo_snd_snd : foo.snd.snd = 4
438505
```

test/simps.lean

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,19 @@ run_cmd do
237237
"Invalid simp-lemma specify.specify1_fst_fst.
238238
Projection fst doesn't exist, because target is not a structure.",
239239
success_if_fail_with_msg (simps_tac `specify.specify1 {} ["foo_fst"])
240-
"Invalid simp-lemma specify.specify1_foo_fst. Projection foo doesn't exist.",
240+
"Invalid simp-lemma specify.specify1_foo_fst. Structure prod does not have projection foo.
241+
The known projections are:
242+
[fst, snd]
243+
You can also see this information by running
244+
`initialize_simps_projections prod`.
245+
Note: the projection names used by @[simps] might not correspond to the projection names in the structure.",
241246
success_if_fail_with_msg (simps_tac `specify.specify1 {} ["snd_bar"])
242-
"Invalid simp-lemma specify.specify1_snd_bar. Projection bar doesn't exist.",
247+
"Invalid simp-lemma specify.specify1_snd_bar. Structure prod does not have projection bar.
248+
The known projections are:
249+
[fst, snd]
250+
You can also see this information by running
251+
`initialize_simps_projections prod`.
252+
Note: the projection names used by @[simps] might not correspond to the projection names in the structure.",
243253
success_if_fail_with_msg (simps_tac `specify.specify5 {} ["snd_snd"])
244254
"Invalid simp-lemma specify.specify5_snd_snd.
245255
The given definition is not a constructor application:
@@ -586,6 +596,48 @@ initialize_simps_projections equiv
586596

587597
end manual_universes
588598

599+
namespace manual_projection_names
600+
601+
structure equiv (α : Sort*) (β : Sort*) :=
602+
(to_fun : α → β)
603+
(inv_fun : β → α)
604+
605+
local infix ` ≃ `:25 := manual_projection_names.equiv
606+
607+
variables {α β γ : Sort*}
608+
609+
instance : has_coe_to_fun $ α ≃ β := ⟨_, equiv.to_fun⟩
610+
611+
def equiv.symm (e : α ≃ β) : β ≃ α := ⟨e.inv_fun, e.to_fun⟩
612+
613+
/-- See Note [custom simps projection] -/
614+
def equiv.simps.inv_fun (e : α ≃ β) : β → α := e.symm
615+
616+
initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)
617+
618+
run_cmd do
619+
e ← get_env,
620+
data ← simps_get_raw_projections e `manual_projection_names.equiv,
621+
guard $ data.2.map prod.fst = [`apply, `symm_apply]
622+
623+
@[simps {simp_rhs := tt}] protected def equiv.trans (e₁ : α ≃ β) (e₂ : β ≃ γ) : α ≃ γ :=
624+
⟨e₂ ∘ e₁, e₁.symm ∘ e₂.symm⟩
625+
626+
example (e₁ : α ≃ β) (e₂ : β ≃ γ) (x : α) : (e₁.trans e₂) x = e₂ (e₁ x) :=
627+
by simp only [equiv.trans_apply]
628+
629+
example (e₁ : α ≃ β) (e₂ : β ≃ γ) (x : γ) : (e₁.trans e₂).symm x = e₁.symm (e₂.symm x) :=
630+
by simp only [equiv.trans_symm_apply]
631+
632+
-- the new projection names are parsed correctly (the old projection names won't work anymore)
633+
@[simps apply symm_apply] protected def equiv.trans2 (e₁ : α ≃ β) (e₂ : β ≃ γ) : α ≃ γ :=
634+
⟨e₂ ∘ e₁, e₁.symm ∘ e₂.symm⟩
635+
636+
-- initialize_simps_projections equiv
637+
638+
end manual_projection_names
639+
640+
589641
-- test transparency setting
590642
structure set_plus (α : Type) :=
591643
(s : set α)

0 commit comments

Comments
 (0)