Zulip Chat Archive
Stream: mathlib4
Topic: notation capturing type information
Jireh Loreaux (Jul 17 2023 at 23:43):
How can I capture type information for pieces involved in notation? For example, let's say I have some foo : Type → Type
and some bar {β : Type} (α : Type) (b : β) (f : foo α) : β
. For elaboration reasons in my actual example, α
has to be explicit despite f
. How can I create notation like this:
notation "my_bar_notation" (f : foo α) b => bar α b f
That is, I want to look at the type of f
, see that it has type foo α
for some α
, and then use α
in the definition of the notation.
Jireh Loreaux (Jul 17 2023 at 23:46):
Note, I know this is possible with metaprogramming, I'm just hoping we have a nice shortcut syntax that works for notation
.
Adam Topaz (Jul 18 2023 at 00:04):
Can you just replace alpha with an underscore in the notation decl, or does that not elaborate properly?
Jireh Loreaux (Jul 18 2023 at 00:20):
I get an "expected =>" as soon as I put in the parentheses, but also, in my actual example, I have something more like foo : Type → Type → Type
and I want (f : foo α α)
.
Adam Topaz (Jul 18 2023 at 00:28):
What I meant is something like this:
def foo : Type → Type := sorry
def bar {β : Type} (α : Type) (b : β) (f : foo α) : β := sorry
notation "my_bar_notation" f b => bar _ b f
Jireh Loreaux (Jul 18 2023 at 01:21):
That causes the same elaboration problems as before because bar
needs α
to be explicit. For context, in my actual use case, bar α b
is a bundled morphism between foo α
and β
. So when you write bar _ b f
Lean can't seem to infer the hole.
To un- #xy, in #5750 I define:
class CFCCoreClass (R : Type _) {A : Type _} [CommSemiring R] [StarRing R] [TopologicalSpace R]
[TopologicalSemiring R] [ContinuousStar R] [Ring A] [StarRing A] [TopologicalSpace A]
[Algebra R A] (a : A) where
toStarAlgHom : C(spectrum R a, R) →⋆ₐ[R] A
hom_continuous : Continuous toStarAlgHom
hom_map_X : toStarAlgHom (toContinuousMapOnAlgHom (spectrum R a) X) = a
def cfc₁ (R : Type _) {A : Type _} [CommSemiring R] [StarRing R] [TopologicalSpace R]
[TopologicalSemiring R] [ContinuousStar R] [Ring A] [StarRing A] [TopologicalSpace A]
[Algebra R A] (a : A) [CFCCoreClass R a] : C(spectrum R a, R) →⋆ₐ[R] A :=
CFCCoreClass.toStarAlgHom
Here, if f : C(spectrum R a, R)
then Lean needs R
to be explicit in cfc₁
in order to elaborate cfc₁ R a f
. But really, the type of f
itself contains the R
datum (and a
too in fact), so it would be nice to just provide some notation so that Lean can look at that the type of f
and fill in these values.
Mario Carneiro (Jul 18 2023 at 01:24):
can you give a MWE?
Mario Carneiro (Jul 18 2023 at 01:24):
(more precisely, does the foo/bar MWE above actually exhibit the issue you describe?)
Adam Topaz (Jul 18 2023 at 01:25):
Would the following solution work?
def foo : Type → Type → Type := sorry
def bar {β : Type} (α α' : Type) (b : β) (f : foo α α') : β := sorry
abbrev bar' {β : Type} (α : Type) (b : β) (f : foo α α) : β := bar α α b f
notation "my_bar_notation" f b => bar' _ b f
Mario Carneiro (Jul 18 2023 at 01:26):
there is also type_of%
for getting the type of a value, although I can't tell if that is sufficient for your use case
Jireh Loreaux (Jul 18 2023 at 01:31):
I need to put kids to bed, but I'll come back to this in a bit.
Jireh Loreaux (Jul 18 2023 at 03:47):
@Mario Carneiro okay, I spent a bunch of time trying to minimize the problem. Here's what I came up with: docs#Module.Dual.instFunLikeDual breaks things
import Mathlib.Algebra.Module.LinearMap
-- these are the imports of `Mathlib.LinearAlgebra.Dual`
import Mathlib.LinearAlgebra.FiniteDimensional
import Mathlib.LinearAlgebra.Projection
import Mathlib.LinearAlgebra.SesquilinearForm
import Mathlib.RingTheory.Finiteness
import Mathlib.LinearAlgebra.FreeModule.Finite.Basic
section MyDual -- copied from `Mathlib.LinearAlgebra.Dual`
namespace Module
universe u v
variable (R : Type u) (M : Type v)
variable [CommSemiring R] [AddCommMonoid M] [Module R M]
@[reducible]
def Dual :=
M →ₗ[R] R
-- this instance breaks `bar'` below.
instance : FunLike (Dual R M) M fun _ => R :=
inferInstanceAs (FunLike (M →ₗ[R] R) M fun _ => R)
end Module
end MyDual
variable (R A : Type _) [Semiring R] [Semiring A] [Module R A]
def foo : R →* A := 1
def bar : R →ₗ[R] A := 0
variable {R}
def foo' : R →* A := 1
def bar' : R →ₗ[R] A := 0
example (f : R) : foo R A f = foo R A f := rfl -- works
example (f : R) : foo' A f = foo R A f := rfl -- works
example (f : R) : bar R A f = bar R A f := rfl -- works
example (f : R) : bar' A f = bar R A f := sorry
Mario Carneiro (Jul 18 2023 at 04:01):
the instance is very strange, you can prove it using inferInstance
alone because Dual
is reducible
Mario Carneiro (Jul 18 2023 at 04:10):
but there also seems to be a bug here, adding the bad instance shouldn't prevent the real one from being found
Jireh Loreaux (Jul 18 2023 at 19:19):
Yeah, it's a bit weird because adding
@[reducible] def SynHom (α : Type u) (β : Type v) [Monoid α] [Monoid β] := α →* β
instance notBad [Monoid α] [Monoid β] : FunLike (SynHom α β) α fun _ => β := inferInstance
doesn't break foo'
.
Jireh Loreaux (Jul 18 2023 at 19:23):
Also, I think there may lie lurking other instances of this bug. Disabling that FunLike
instance locally doesn't fix the elaboration problems for my StarAlgHom
case.
Jireh Loreaux (Jul 18 2023 at 19:28):
Out of my depth here, but could the problem be that something uses a whnfR
where it should be using whnfI
?
Jireh Loreaux (Jul 18 2023 at 20:54):
Can we have a linter that catches whenever an instance is declared but it could have been filled with inferInstance
?
Matthew Ballard (Jul 18 2023 at 20:56):
inferInstanceAs
would be nice too but probably very hard
Alex J. Best (Jul 18 2023 at 21:09):
Don't we sometimes add these short circuit instances on purpose though? Such a linter would be easy to write but I'm not sure if there would be too many false positives?
Scott Morrison (Jul 19 2023 at 04:08):
We do have lots of short circuit instances, but it would be great if they were documented, and a linter would help with this.
Scott Morrison (Jul 19 2023 at 04:09):
Also, the linter could check for and allow := inferInstance
as the definition.
Jireh Loreaux (Jul 20 2023 at 17:08):
I have no experience writing a linter, but since Alex says it's easy, maybe I'll have a go at writing it. Which existing linter might be a good one to read and learn from?
Alex J. Best (Jul 20 2023 at 17:32):
It might be easiest to start from scratch. The most similar in spirit is one of the simp linters, the one that says "simp can prove this", we really want a version that says "inferInstance" can prove this, docs#Std.Tactic.Lint.simpNF. But the simp linter is quite a bit more complicated than we need here.
I think you just need to check if the declaration is a def or a theorem, and check that it is an instance, and if so get the type, make a new MVarId representing a term of that type and call introsN to get a new metavariable representing the goal of the typeclass in the context of the hypotheses, then try to apply the term inferInstance
(docs#Lean.MVarId.apply, making sure you are in the withContext
of that MVarId) and see that you get no goals left (i.e. apply returns an empty list).
I have to run now, but will happily say more detail later if that is helpful.
Jireh Loreaux (Jul 20 2023 at 18:05):
Thanks for the outline! I'll give it a try.
Kyle Miller (Jul 20 2023 at 18:19):
@Jireh Loreaux In case it helps, here's a command that can tell you if an instance is superfluous:
import Lean
open Lean Meta Elab
elab "#is_superfluous " id:ident : command => withoutModifyingEnv <| Command.runTermElabM fun _ => do
let name ← resolveGlobalConstNoOverloadWithInfo id
let some decl := (← getEnv).find? name | throwError "(internal error) no such declaration"
let type := decl.type
unless (← isClass? type).isSome do throwError "{name} does not give a class instance"
modifyEnv Meta.instanceExtension.pushScope
try
let s ← (instanceExtension.getState (← getEnv)).erase name
modifyEnv fun env => instanceExtension.modifyState env fun _ => s
let inst? ← trySynthInstance type
if let .some inst := inst? then
logWarning m!"{name} is superfluous. Independently provided by{indentD inst}"
else
logInfo m!"{name} is not superfluous"
finally
modifyEnv Meta.instanceExtension.popScope
#is_superfluous instAddNat
-- instAddNat is not superfluous
instance myGreatInstance : Add Nat := inferInstance
#is_superfluous myGreatInstance
-- myGreatInstance is superfluous. Independently provided by instAddNat
instance anotherGreatInstance (x y : Nat) : Decidable (x = y) := inferInstance
#is_superfluous anotherGreatInstance
/-
anotherGreatInstance is superfluous. Independently provided by
fun x y ↦ instDecidableEqNat x y
-/
Kyle Miller (Jul 20 2023 at 18:21):
The key is (1) temporarily removing the instance under question and (2) using trySynthInstance
on the type of the declaration (which is able to handle foralls, so no need use introN, and it's basically the core to inferInstance
)
Last updated: Dec 20 2023 at 11:08 UTC