Zulip Chat Archive
Stream: lean4
Topic: Qq matching on -> error: cannot quote level mvar
Bob Rubbens (Feb 17 2024 at 13:08):
Hi all, another metaprogramming question.
I've been succesfully using Qq, and it's been very nice. However somehow I can't get it to pattern match on an implication. Here's an example:
import Lean
import Qq
open Lean Elab Tactic Term Meta PrettyPrinter Qq
def simplifyLocalDecl (g : MVarId) (localDecl : LocalDecl): TacticM (Option (List MVarId)) := do
let h := localDecl.fvarId
let xx ← inferTypeQ (.fvar h)
let res ← match xx with
| ⟨0, ~q(_ → _), _⟩ => return some []
| _ => return none
I get an error on the match keyword: cannot quote level mvar ?u.817
. For the and, or, and not operators this worked just fine.
As a side question, is there some documentation somewhere on using Qq? The readme in the quote4 repo is pretty clear, but it says pattern matching doesn't work yet, yet it clearly does for me, so I'm guessing the readme is out of date a bit?
Edit: Writing it with a universal quantifier gives the same error: | ⟨0, ~q(∀ t, $b t), _⟩ => return none
. Pretty sure I'm not reading some fine print somewhere again...
Bob Rubbens (Feb 17 2024 at 13:39):
I worked around it for now by just using plain pattern matching:
| .forallE _ binderType body _ =>
let binderTypeType ← inferType binderType
let anteProp ← isDefEq binderTypeType q(Prop)
let bodyType ← inferType body
let conseProp ← isDefEq bodyType q(Prop)
if anteProp ∧ conseProp
then return some (← localRewrite g localDecl ``imp_or)
else return none
I was rewriting ->
to or
expressions. I'm guessing dependent arrows are a fundamental thing in lean/its kernel so that might make pattern matching on it difficult?
Eric Wieser (Feb 17 2024 at 13:57):
Qq pattern matching on functions is pretty difficult right now, @Yaël Dillies has been struggling with this too
Bob Rubbens (Feb 17 2024 at 13:57):
Okay, then if my workaround isn't too silly I'm happy :smiley:
Notification Bot (Feb 17 2024 at 13:58):
Bob Rubbens has marked this topic as resolved.
Eric Wieser (Feb 17 2024 at 14:00):
Here's a mwe in case anyone wants to fiddle around with it:
import Lean
import Qq
open Lean Elab Tactic Term Meta PrettyPrinter Qq
def isImp (p : Q(Prop)) : MetaM Bool := do
let res ← match p with
| ~q((_ : Prop) → _) => return true
| _ => return false
#eval isImp q(True → True) -- wrong answer!
Eric Wieser (Feb 17 2024 at 14:03):
Oh, this works!
def isImp (p : Q(Prop)) : MetaM <| Option (Q(Prop) × Q(Prop)) := do
match p with
| ~q((($P) : Prop) → $Q) => do
return some (P, Q)
| _ =>
return none
#eval isImp q(True → False)
#eval isImp q(True ∧ False)
Eric Wieser (Feb 17 2024 at 14:03):
Those ()
s around $P
are the trick
Yaël Dillies (Feb 17 2024 at 14:03):
Stealing it immediately for my positivity extensions
Yaël Dillies (Feb 17 2024 at 14:04):
Wait, I have one more complication in my use case: not all universes are Prop
Yaël Dillies (Feb 17 2024 at 14:05):
How do I get the universes to work out? If I want to destructure π : Sort w
into α → β
where α : Sort u
and β : Sort v
I somehow need to unify w
with imax u v
.
Eric Wieser (Feb 17 2024 at 14:06):
I guess you'll need ~q((($P) : Sort u) → (($Q) : Sort v))
Eric Wieser (Feb 17 2024 at 14:06):
Is Type
ok for you?
Eric Wieser (Feb 17 2024 at 14:06):
Yaël Dillies said:
How do I get the universes to work out? If I want to destructure
π : Sort w
intoα → β
whereα : Sort u
andβ : Sort v
I somehow need to unifyw
withimax u v
.
This was discussed already in another thread of yours, please link it here when you find it!
Yaël Dillies (Feb 17 2024 at 14:07):
Eric Wieser said:
Is
Type
ok for you?
Yes in fact I'm only dealing with Type _
in my use case
Eric Wieser (Feb 17 2024 at 14:08):
I think imax
would have caused you a lot more pain than max
Yaël Dillies (Feb 17 2024 at 14:11):
Eric Wieser said:
I guess you'll need
~q((($P) : Sort u) → (($Q) : Sort v))
But how do I get u
and v
?
Yaël Dillies (Feb 17 2024 at 14:11):
They don't exist in my context before the match
Eric Wieser (Feb 17 2024 at 14:15):
You create them as metavariables
Eric Wieser (Feb 17 2024 at 14:16):
... as discussed in the previous thread you made somewhere
Notification Bot (Feb 17 2024 at 14:17):
Eric Wieser has marked this topic as unresolved.
Yaël Dillies (Feb 17 2024 at 14:17):
Eric Wieser (Feb 17 2024 at 14:18):
Yes, this message
Yaël Dillies (Feb 17 2024 at 14:23):
Something is wrong here
import Lean
import Qq
open Lean Elab Tactic Term Meta PrettyPrinter Qq
def isFun {u : Level} (π : Q(Type $u)) :
MetaM (Option (Σ (v w : Level), Q(Type v) × Q(Type w))) := do
let v ← mkFreshLevelMVar
let w ← mkFreshLevelMVar
let _ : u =QL max v w := ⟨⟩
match π with
| ~q((($α) : Type v) → (($β) : Type w)) => return .some ⟨v, w, q($α), q($β)⟩
| _ => return none
#eval isFun q(Unit → Unit) -- none
Eric Wieser (Feb 17 2024 at 14:25):
Yeah, I think the universe variables don't unify properly
Eric Wieser (Feb 17 2024 at 14:40):
You could #print the resulting isFun
, and reduce the failing meta code to a ~q
-free mwe
Yaël Dillies (Feb 17 2024 at 14:44):
This seems to work?
def isFun {u : Level} (π : Q(Sort $u)) :
MetaM (Option (Σ (v w : Level), Q(Sort v) × Q(Sort w))) := do
match ← whnfR π with
| Expr.forallE _ α β _ =>
let .sort v ← inferType α | return none
let .sort w ← inferType β | return none
return .some ⟨v, w, α, β⟩
| _ => return none
#eval isFun q(Unit → Unit)
/-
some ⟨Lean.Level.succ
(Lean.Level.zero), ⟨Lean.Level.succ (Lean.Level.zero),
(Lean.Expr.const `Unit [], Lean.Expr.const `Unit [])⟩⟩
-/
Eric Wieser (Feb 17 2024 at 14:45):
Oh, I know it's possible to do without Qq; my question is why Qq's generated code doesn't work
Eric Wieser (Feb 17 2024 at 14:46):
(that approach doesn't work if π
has mdata
or is a metavariable though)
Eric Wieser (Feb 17 2024 at 14:46):
My guess is that the issue is some kind of max u1 v1 =?= max u2 v2
unification failure.
Yaël Dillies (Feb 17 2024 at 14:47):
Edited to add mdata
handling
Eric Wieser (Feb 17 2024 at 14:48):
I think whnfR
is probably the better approach
Yaël Dillies (Feb 17 2024 at 14:50):
Eric Wieser said:
You could #print the resulting
isFun
, and reduce the failing meta code to a~q
-free mwe
Am I supposed to do that from the raw expression?
Eric Wieser (Feb 17 2024 at 16:19):
Hmm, the version I had earlier printed without raw expressions...
Eric Wieser (Feb 17 2024 at 16:20):
Using return .some ⟨v, w, α, β⟩
instead of ⟨v, w, q($α), q($β)⟩
fixes the printer
Eric Wieser (Feb 17 2024 at 16:22):
Replacing withNewMCtxDepth _ false
with withNewMCtxDepth _ true
seems to fix it
Patrick Massot (Feb 17 2024 at 16:40):
Yaël and Eric, it would be really nice to write Qq documentation with everything you understood.
Yaël Dillies (Feb 17 2024 at 16:42):
I was contemplating doing that, actually
Yaël Dillies (Feb 17 2024 at 16:42):
I have already spotted some existing docstrings that are confusing at best
Yaël Dillies (Feb 17 2024 at 16:44):
It's quite funny reading my messages from a few months ago. I have definitely learned things! but not enough apparently :sad:
Yaël Dillies (Feb 17 2024 at 16:48):
Okay, so fixing the Qq version and simplifying it, I get
import Lean
import Qq
open Lean Elab Tactic Term Meta PrettyPrinter Qq
def isFun {u : Level} (π : Q(Type $u)) : MetaM (Option (Σ v w : Level, Q(Type v) × Q(Type w))) := do
let v ← mkFreshLevelMVar
let w ← mkFreshLevelMVar
withNewMCtxDepth
(do
let α ← mkFreshExprMVar (some (.sort (.succ v)))
let β ← mkFreshExprMVar (some (.sort (.succ w)))
match ← withReducible (isDefEq (.forallE .anonymous α β .default) π) with
| true => pure (some ⟨v, w, ← instantiateMVars α, ← instantiateMVars β⟩)
| false => pure none)
true
#eval isFun q(Unit → Nat)
Yaël Dillies (Feb 17 2024 at 16:48):
It looks pretty similar to the version I came up with by hand, but looks a bit more robust.
Eric Wieser (Feb 17 2024 at 16:53):
Patrick Massot said:
Yaël and Eric, it would be really nice to write Qq documentation with everything you understood.
All my writing energy is currently directed at a thesis, but this is high on my todo list. Do you think it has a better home in Qq itself, or in the metaprogramming book?
Eric Wieser (Feb 17 2024 at 16:56):
Presumably withNewMCtxDepth false
is fine if the level metavariables are created inside the context
Eric Wieser (Feb 17 2024 at 16:58):
I think probably Qq is doing the right thing by refusing to unify universe metavariables that it doesn't own; but the problem is that unlike expression metavariables, it doesn't provide the user with any way to pass over ownership
Eric Wieser (Feb 17 2024 at 16:59):
Perhaps what's missing is a ~ql(max $u $v)
match clause
Eric Wieser (Feb 17 2024 at 16:59):
As a reminder, Qq already has ql
:
variable {u v : Level}
#check ql(max u v)
Yaël Dillies (Feb 17 2024 at 17:01):
Here's a MWE, with all the extra typeclasses stripped away:
import Mathlib.Tactic.Positivity.Core
section IsFun
open Lean Elab Tactic Term Meta PrettyPrinter Qq
def isFun {u : Level} (π : Q(Type $u)) : MetaM (Option (Σ v w : Level, Q(Type v) × Q(Type w))) := do
let v ← mkFreshLevelMVar
let w ← mkFreshLevelMVar
withNewMCtxDepth
(do
let α ← mkFreshExprMVar (some (.sort (.succ v)))
let β ← mkFreshExprMVar (some (.sort (.succ w)))
match ← withReducible (isDefEq (.forallE .anonymous α β .default) π) with
| true => pure (some ⟨v, w, ← instantiateMVars α, ← instantiateMVars β⟩)
| false => pure none)
true
end IsFun
section Defs
variable {α β : Type*}
def conv (f g : α → β) : α → β := sorry
variable [OrderedAddCommMonoid β] {f g : α → β}
lemma conv_nonneg (hf : 0 ≤ f) (hg : 0 ≤ g) : 0 ≤ conv f g := sorry
lemma conv_nonneg_of_pos_of_nonneg (hf : 0 < f) (hg : 0 ≤ g) : 0 ≤ conv f g := conv_nonneg hf.le hg
lemma conv_nonneg_of_nonneg_of_pos (hf : 0 ≤ f) (hg : 0 < g) : 0 ≤ conv f g := conv_nonneg hf hg.le
lemma conv_pos (hf : 0 < f) (hg : 0 < g) : 0 < conv f g := sorry
end Defs
namespace Mathlib.Meta.Positivity
open Lean Meta Qq
@[positivity conv _ _] def evalConv : PositivityExt where eval {u π} zπ pπ e := do
let .some ⟨v, w, ~q($α), ~q($β)⟩ ← isFun π | throwError "not `conv`"
have : u =QL max v w := ⟨⟩
have : $π =Q ($α → $β) := ⟨⟩
match e with
/-
don't know how to synthesize placeholder
context:
case m
«$$v»«$$w»: Level
$$x✝¹$$x✝: Expr × Bool
⊢ Sort ?u.6232
-/
| ~q(@conv $α $β ($f : Q($α → $β)) ($g : Q($α → $β))) => _
| _ => throwError "not `conv`"
end Mathlib.Meta.Positivity
section Examples
variable {α β : Type*} [OrderedAddCommMonoid β] {f g : α → β}
-- failed to prove positivity/nonnegativity/nonzeroness
example (hf : 0 ≤ f) (hg : 0 ≤ g) : 0 ≤ conv f g := by positivity
example (hf : 0 < f) (hg : 0 ≤ g) : 0 ≤ conv f g := by positivity
example (hf : 0 ≤ f) (hg : 0 < g) : 0 ≤ conv f g := by positivity
example (hf : 0 < f) (hg : 0 < g) : 0 < conv f g := by positivity
end Examples
Yaël Dillies (Feb 17 2024 at 17:01):
I have no idea where this error is coming from
Eric Wieser (Feb 17 2024 at 17:11):
This compiles:
@[positivity conv _ _] def evalConv : PositivityExt where eval {u π} zπ pπ e := do
let .some ⟨v, w, ~q($α), ~q($β)⟩ ← isFun π | throwError "not `conv`"
have : u =QL max v w := ⟨⟩
have : $π =Q ($α → $β) := ⟨⟩
match q($e) with
| ~q(@conv.{w,v} _ _ $f $g) => sorry
| _ => throwError "not `conv`"
Jannis Limperg (Feb 17 2024 at 17:12):
(FYI, you can write withNewMCtxDepth (allowLevelAssigments := true) do ...
, which reads slightly nicer.)
Yaël Dillies (Feb 17 2024 at 17:50):
In fact,
def evalConv : PositivityExt where eval {u π} zπ pπ e := do
let .some ⟨v, w, ~q($α), ~q($β)⟩ ← isFun π | throwError "not `conv`"
have : u =QL max v w := ⟨⟩
have : $π =Q ($α → $β) := ⟨⟩
match q($e) with
| ~q(conv $f $g) => sorry
| _ => throwError "not `conv`"
works too
Yaël Dillies (Feb 17 2024 at 17:50):
Why is q($e)
not behaving the same as e
?
Yaël Dillies (Feb 17 2024 at 17:58):
Yaël Dillies said:
In fact, [...] works too
No it doesn't, the Qq ascriptions are wrong because new types are introduced.
Yaël Dillies (Feb 17 2024 at 18:27):
New MWE, exhibiting a different error:
def evalConv : PositivityExt where eval {u π} zπ pπ e := do
let .some ⟨v, w, ~q($α), ~q($β)⟩ ← isFun π | throwError "not `conv`"
have : u =QL max v w := ⟨⟩
have : $π =Q ($α → $β) := ⟨⟩
match q($e) with
| ~q(@conv $α $β $f $g) => do
let _ ← synthInstanceQ q(OrderedAddCommMonoid $β)
let _ ← synthInstanceQ q(PartialOrder $β)
match ← core q(inferInstance) q(inferInstance) f with
| .positive rf => do
match ← core q(inferInstance) q(inferInstance) g with
| .positive rg => do
let rf : Q(0 < $f) := rf
let rg : Q(0 < $g) := rg
/-
application type mismatch
conv_pos «$rf»
argument
«$rf»
has type
@OfNat.ofNat («$α» → «$β») 0
(@Zero.toOfNat0 («$α» → «$β») (@Pi.instZero «$α» (fun a ↦ «$β») fun i ↦ AddMonoid.toZero)) <
«$f» : Prop
but is expected to have type
@OfNat.ofNat («$α» → «$β») 0
(@Zero.toOfNat0 («$α» → «$β») (@Pi.instZero «$α» (fun a ↦ «$β») fun i ↦ AddMonoid.toZero)) <
?m.21991 : Prop
-/
return .positive q(@conv_pos $α $β _ _ _ $rf $rg)
| .nonnegative rg => sorry
| _ => return .none
| .nonnegative rf => sorry
| _ => return .none
| _ => throwError "not `conv`"
Yaël Dillies (Feb 17 2024 at 18:27):
The full MWE is available at https://github.com/YaelDillies/LeanAPAP/blob/conv_positivity/LeanAPAP/MWE.lean
Patrick Massot (Feb 18 2024 at 03:04):
Eric Wieser said:
Patrick Massot said:
Yaël and Eric, it would be really nice to write Qq documentation with everything you understood.
All my writing energy is currently directed at a thesis, but this is high on my todo list. Do you think it has a better home in Qq itself, or in the metaprogramming book?
I would aim for an independent document but this is really up to you.
Yaël Dillies (Feb 18 2024 at 22:19):
Yaël Dillies said:
New MWE, exhibiting a different error: ...
I would still use some wisdom on this one :grimacing:
Eric Wieser (Jul 14 2024 at 23:28):
I've added some documentation in Qq itself in leanprover-community/quote4#47
Eric Wieser (Jul 14 2024 at 23:29):
This is definitely not reaching as far as the stuff in the thread above, but does at least mean that hovering over ~q
gives anything at all.
Last updated: May 02 2025 at 03:31 UTC