Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Modifying the `reassoc` attribute for monadic computations
Brendan Seamas Murphy (Nov 17 2023 at 05:20):
I would like to have a variant of reassoc
which generates associated lemmas for theorems like ∀ (s₁ s₂ : σ), (do set s₁; set s₂ : m PUnit) = set s₂
which states a universally quantified equality between stuff of the form m α
for some monad m
(possible being quantified over). Here is an excerpt of my intended applications
class LawfulMonadStateOf (σ : Type u) (m : Type u → Type v)
[MonadStateOf σ m] [Monad m] extends LawfulMonad m where
set_set : ∀ (s₁ s₂ : σ), (do set s₁; set s₂ : m PUnit) = set s₂
set_get : ∀ (s : σ), (do set s; get : m σ) = (do set s; pure s)
get_set : (do let s ← (get : m σ); set s) = pure PUnit.unit
get_get_pair : (do let s₁ ← (get : m σ); let s₂ ← get; pure (s₁, s₂))
= (do let s ← get; pure (s, s))
modifyGet_def : ∀ {α} (f : σ → α × σ),
(modifyGet f : m α) = (do let p := f (← get); set p.snd; pure p.fst)
attribute [reassocM (attr := simp)] set_set
-- attribute [reassocM (attr := simp)] set_get get_set modifyGet_def
And here is my attempt at modifying the Mathlib.Tactic.CategoryTheory.Assoc
file:
import Mathlib.Util.AddRelatedDecl
import Mathlib.Lean.Meta.Simp
import Mathlib.Lean.Meta
open Lean Meta Elab Tactic
open Mathlib.Tactic
namespace LawfulMonad
universe u v
theorem eq_bind {m : Type u → Type v} [Monad m] {α} {x y : m α} (w : x = y)
{β} (f : α → m β) : x >>= f = y >>= f := by rw [w]
def monadSimp (e : Expr) : MetaM Simp.Result :=
getAllSimpDecls `monad_norm >>=
(simpOnlyNames . e (config := { decide := false }))
def reassocMExpr (e : Expr) : MetaM Expr := do
mapForallTelescope (fun e => do simpType monadSimp (← mkAppM ``eq_bind #[e])) e
syntax (name := reassocM) "reassocM" (" (" &"attr" ":=" Parser.Term.attrInstance,* ")")? : attr
initialize registerBuiltinAttribute {
name := `reassocM
descr := ""
applicationTime := .afterCompilation
add := fun src ref kind => match ref with
| `(attr| reassocM $[(attr := $stx?,*)]?) => MetaM.run' do
if (kind != AttributeKind.global) then
throwError "`reassocM` can only be used as a global attribute"
addRelatedDecl src "_assoc" ref stx? fun type value levels => do
pure (← reassocMExpr (← mkExpectedTypeHint value type), levels)
| _ => throwUnsupportedSyntax }
open Term in
elab "reassocM_of% " t:term : term => do
reassocMExpr (← elabTerm t none)
end LawfulMonad
I'm currently getting an error that looks like
... has type
(do set s₁; set s₂) = set s₂ : Prop
but is expected to have type
@Eq.{?u.1614 + 1} (?m ?α) ?x ?y : Prop
so my assumption is that lean is having trouble inferring the m
and α
arguments to eq_bind
in reassocMExpr
. Does anyone know how I can make this work?
Kyle Miller (Nov 17 2023 at 05:28):
You need to set up the additional binders for m
and α
so that they come out as universally quantified
Brendan Seamas Murphy (Nov 17 2023 at 05:29):
I thought that's what mapForallTelescope
was doing, but I guess I misunderstood?
Kyle Miller (Nov 17 2023 at 05:29):
You could look at the elementwise attribute instead, which might be more similar to what you want to do
Kyle Miller (Nov 17 2023 at 05:32):
I might be misunderstanding what you need to do exactly -- maybe take a look at mkAppOptM
as well, if you know the m
, etc., that you need to pass in.
Brendan Seamas Murphy (Nov 17 2023 at 05:34):
It's supposed to work for theorems involving universally quantified monads or even multiple monads. I guess what I had in mind is that it would figure out the monad by matching the type of the eqation (after all universal quantifiers) with m a
. So maybe I'll try to do that match and then use mkAppOptM
with the discovered m
passed in?
Kyle Miller (Nov 17 2023 at 05:35):
(Btw, mapForallTelescope
is able to take the binders from the type of e
itself, but if you need to introduce new variables you'll have to do that inside the body of the fun
using withLocalDecl
to create the local binding and then use mkForallFVars
or mkLambdaFVars
(depending on what you want) before leaving withLocalDecl
)
Kyle Miller (Nov 17 2023 at 05:38):
One way you could do this is by using forallMetaTelescope
on the type of eq_bind
, unifying the correct argument with e
, and then passing the remaining arguments to mkAppN ``eq_bind metavars
, and then use mkLambdaFVars
on this with the unsolved-for metavariables. It turns out mkLambdaFVars
is able to take metavariables too, despite the name.
Kyle Miller (Nov 17 2023 at 05:40):
That's for the fully general multi-monad lemma, if I'm understanding this right.
Brendan Seamas Murphy (Nov 17 2023 at 05:41):
also re: what I want this to do, an example would be to generate
@[simp]
theorem set_set_assoc {σ : Type u} {m : Type u → Type v} [MonadStateOf σ m]
[Monad m] [LawfulMonad m] [LawfulMonadStateOf σ m]
: ∀ (s₁ s₂ : σ) {β} (f : PUnit → m β), (do set s₁; let u ← set s₂; f u) = (do let u ← set s₂; f u)
from the annotation attribute [reassocM (attr := simp)] set_set
, where set_set : ∀ (s₁ s₂ : σ), (do set s₁; set s₂ : m PUnit) = set s₂
is a field of the typeclass LawfulMonadStateOf
Brendan Seamas Murphy (Nov 17 2023 at 05:42):
as a less simple example, I think this should be able to generate
@[simp]
theorem get_set_pure_eq_get_assoc {α} (f : σ → m α)
: (do let s ← get; set s; f s) = get >>= f
from get_set_pure_eq_get : (do let s ← get; set s; pure s) = (get : m σ)
Brendan Seamas Murphy (Nov 17 2023 at 05:44):
The multi monad stuff would be if I wanted to use this for a hypothetical LawfulMonadLift
class, where axioms or theorem statements quantify over multiple monads
Kyle Miller (Nov 17 2023 at 05:49):
@Brendan Seamas Murphy I get it now -- it was a failure to do higher-order unification I think. Here's a replacement function:
def reassocMExpr (e : Expr) : MetaM Expr := do
mapForallTelescope (fun e => do
let ety ← instantiateMVars (← inferType e)
let some (ty, _, _) := ety.eq? | throwError "Expecting an equality, not{indentD ety}"
let .app m _ := ty | throwError "Expecting an equality for a monad, not{indentD ty}"
simpType monadSimp (← mkAppOptM ``eq_bind #[some m, none, none, none, none, some e])) e
Kyle Miller (Nov 17 2023 at 05:50):
Does this look right?
attribute [reassocM (attr := simp)] LawfulMonadStateOf.set_set
#print LawfulMonadStateOf.set_set_assoc
/-
⊢ ∀ {σ : Type u} {m : Type u → Type v} [inst : MonadStateOf σ m] [inst_1 : Monad m] [self : LawfulMonadStateOf σ m]
(s₁ s₂ : σ) {β : Type u} (f : PUnit → m β),
(do
set s₁
set s₂) >>=
f =
set s₂ >>= f
-/
Brendan Seamas Murphy (Nov 17 2023 at 05:51):
Hm, it should be applying the simplifier with bind_assoc. Maybe the monad_norm
tag needs to be in scope when I define reassocM
?
Brendan Seamas Murphy (Nov 17 2023 at 05:51):
I don't understand metaprogramming in lean well :sweat_smile:
Kyle Miller (Nov 17 2023 at 05:51):
I just have the code you gave me -- I don't have any monad_norm
lemmas
Brendan Seamas Murphy (Nov 17 2023 at 05:52):
ahh, of course
Brendan Seamas Murphy (Nov 17 2023 at 05:52):
That looks awesome then, thanks!
Kyle Miller (Nov 17 2023 at 05:53):
If you make the m
argument explicit in eq_bind
, then you can switch the mkAppOptM
to mkAppM ``eq_bind #[m, e]
Brendan Seamas Murphy (Nov 17 2023 at 05:54):
Is there a reason that's better? Also, it simplifies correctly when in a context with monad_norm
lemmas so hooray!
Kyle Miller (Nov 17 2023 at 05:55):
I feel like it makes the meta code cleaner, rather than needing to count implicit arguments carefully. I think it's good making the lemmas that support meta code be tailored to it.
Brendan Seamas Murphy (Nov 17 2023 at 05:55):
Makes sense, I'll change that then. Thanks for the help!
Kyle Miller (Nov 17 2023 at 05:55):
You don't need to, but it's something you could do if you wanted.
Last updated: Dec 20 2023 at 11:08 UTC