Zulip Chat Archive

Stream: lean4

Topic: Compiling match with 6 Qq patterns takes 50s


Bob Rubbens (Feb 17 2024 at 19:46):

Good evening everyone, I have another metaprogramming question, I have I'm not through my question credits yet...

I'm at the following program now:

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 none
  | 0, ~q(¬(_  _)), _ => return none
  | 0, ~q(¬¬_), _ => return none
  | 0, ~q(¬$p), _ => return none
  | 0, ~q(_  (_  _)), _ => return none -- A
  | 0, ~q((_  _)  _), _ => return none -- B
  | _, _, _ => return none

It does exactly what I want, but it's a bit slow to compile. Without the lines marked A and B, its a second or so until the yellow bar on the left of the vscodeditor and the "busy" tooltip disappears. With the lines marked A and B included, it takes about 50 seconds. My laptop is by no means beefy but performance hasn't been a problem for me (yet...).

In the MWE I factored out the two or patterns into a separate def that just returns a bool if it matches, which brings compile time down to 5, so I can probably continue with that workaround. But it's a bit of a strange case. Is this just me abusing/misusing Qq or should I post an issue for this somewhere?

Eric Wieser (Feb 19 2024 at 23:04):

Is the issue the patterns themselves, or just the fact that there are lots of patterns?

Kim Morrison (Feb 19 2024 at 23:59):

Matches with many patterns, and each pattern looking deeply, have a known performance limitation. We'd like to have a language level fix, but realistically this is not within the next 6 months.

My personal recommendation is to avoid the quote4 library, and instead use nested match statements using e.getAppFnArgs.

Eric Wieser (Feb 20 2024 at 03:51):

I'm surprised the depth of the pattern affects the compilation time in a major way

Eric Wieser (Feb 20 2024 at 03:53):

I wonder if Qq is just missing some type annotations internally that would stop what I assume is elaborate blow up

Eric Wieser (Feb 20 2024 at 03:55):

I agree that some of the issues with ~q (#21, #36) are fixable only with language-level changes; do notation needs do extension hooks to allow custom matching, otherwise it's basically impossible to handle all the bells and whistles of do notation (let mut, return) correctly

Eric Wieser (Feb 20 2024 at 07:27):

Eric Wieser said:

Is the issue the patterns themselves, or just the fact that there are lots of patterns?

Filed as https://github.com/leanprover-community/quote4/issues/37 with a mwe

Jannis Limperg (Feb 20 2024 at 10:16):

I'd be very happy to supervise a project to implement matching up to reducible defeq 'done right', if anyone is interested.

Eric Wieser (Feb 20 2024 at 10:40):

I think the hard part is the syntax change to do notation and getting buy-in (of time spent reviewing an important part of Lean) from the FRO

Jannis Limperg (Feb 20 2024 at 12:16):

Solving these issues would also be nice, but they don't bother me too much. What bothers me more, and what the proposed project would address, is that Qq matching is very wasteful. Currently, the match in the example

import Lean
import Qq

open Lean Lean.Elab.Tactic Qq

def test (e : Expr) : TacticM Unit := do
  let 0, t, _  inferTypeQ e
    | return
  match t with
  | ~q(And _ _) => f1
  | ~q(Or _ (And _ _)) => f2
  | ~q(Or _ (Or _ _)) => f3
  | _ => sorry

#print test

essentially compiles to

let pat1 <- elabPattern `(And _ _)
if isDefEq e pat1 then
  f1
else
  let pat2 <- elabPattern `(Or _ (And _ _))
  if isDefEq e pat2 then
    f2
  else
    let pat3 <- elabPattern `(Or _ (Or _ _))
    if isDefEq e pat3 then
      f3
    else
      sorry

(Actually, Qq reorders the patterns for some reason.) This is a very effective hack, but what you would write by hand is this:

  match <- whnf t with
  | .app (.app (.const ``And _) _) _ => f1
  | .app (.app (.const ``Or _) _) x =>
    match <- whnf x with
    | .app (.app (.const ``And _) _) _ => f2
    | .app (.app (.const ``Or _) _) _ => f3
    | _ => sorry
  | _ => sorry

So instead of three isDefEqs you have two whnfs, one for each level of the pattern. It's conceptually easy to derive the latter implementation from the ~q match syntax, though the devil is probably in the details.

Jannis Limperg (Feb 20 2024 at 12:26):

Jannis Limperg said:

(Actually, Qq reorders the patterns for some reason.)

Scratch this, I just didn't understand the code.

Eric Wieser (Feb 20 2024 at 12:48):

is that Qq matching is very wasteful

Just to be clear, the title of this thread is about wastefulness at elaboration time, whereas it sounds like you're suggesting reducing wastefulness at runtime. Of course, both would be beneficial!

Eric Wieser (Feb 20 2024 at 12:49):

Solving these issues would also be nice, but they don't bother me too much.

I think return notation doing completely the wrong thing is pretty damning, and a big footgun

Jireh Loreaux (Feb 20 2024 at 13:11):

Sorry, what's the problem with return?

Moritz Doll (Feb 20 2024 at 13:14):

See Eric's example in https://github.com/leanprover-community/quote4/issues/21

Eric Wieser (Feb 20 2024 at 13:15):

The problem is that ¬q doesn't (can't?) participate in the do notation magic, so has to try and build something on top of it. I think this is impossible to do correctly (without other bad choices like duplicating syntax)

Moritz Doll (Feb 20 2024 at 13:16):

the return behaves like rust and not like haskell (if I remember correctly)

Moritz Doll (Feb 20 2024 at 13:21):

and for comparison:

def foo₃ (a : ) : MetaM Bool := do
  let x : Unit  match a with
    | 37 => return true
    | _ => return true
  return false -- `do` element is unreachable

Jannis Limperg (Feb 20 2024 at 13:36):

Eric Wieser said:

is that Qq matching is very wasteful

Just to be clear, the title of this thread is about wastefulness at elaboration time, whereas it sounds like you're suggesting reducing wastefulness at runtime. Of course, both would be beneficial!

Yes, sorry for the confusion. The alternative design I propose might also benefit elaboration, but only as a side effect.


Last updated: May 02 2025 at 03:31 UTC