Zulip Chat Archive

Stream: lean4

Topic: Is `simp_wf` harmless?


Heather Macbeth (Mar 17 2023 at 10:27):

I'd like to propose that decreasing_by build in an application of the simp_wf tactic by default. (This is one of the innards of decreasing_tactic.). It makes the infoview a lot clearer. Here's an example:

def gcd : Nat  Nat  Nat
  | a, 0 => a
  | a, b + 1 => gcd (b + 1) (a % (b + 1))
termination_by gcd a b => b

Compare the goal state for the following two partially-set-up termination proofs (in each case with the cursor right before the sorry):

decreasing_by sorry
/-
a b : Nat
⊢ (invImage (fun a => PSigma.casesOn a fun a snd => snd) instWellFoundedRelation).1 { fst := b + 1, snd := a % (b + 1) }
  { fst := a, snd := Nat.succ b }
-/

and

decreasing_by simp_wf ; sorry
/-
a b : Nat
⊢ a % (b + 1) < Nat.succ b
-/

The latter is much more informative.

Heather Macbeth (Mar 17 2023 at 10:29):

It seems to me that the only reason not to do this would be if there are examples of termination proofs which this simp_wf would make fail. Does anyone have such an example or is the simp_wf indeed harmless?

Heather Macbeth (Mar 17 2023 at 10:53):

Alternatively: is it possible to write my own syntax custom_decreasing_by which does this? I got as far in the source as the place where decreasing_by is defined as a leading_parser but I'm not sure how to write a variant.

Alex Keizer (Mar 17 2023 at 13:17):

Heather Macbeth said:

Alternatively: is it possible to write my own syntax custom_decreasing_by which does this? I got as far in the source as the place where decreasing_by is defined as a leading_parser but I'm not sure how to write a variant.

You should have a look at the elaborator for declaration, that is where the decreasing_by is used. So you'd have to create new parser for def, like

def custom_def            := leading_parser
  "custom_def " >> declId >> ppIndent optDeclSig >> declVal >> optDefDeriving >> customTerminationSuffix

Where customTerminationSuffix is a version of the terminationSuffix parser that uses custom_decreasing_by.
(You might be able to reuse the def keyword here, instead of custom_def; I'm not 100% sure, though, so I just picked a different command to be safe)

Then, you have to write an elaborator for custom_def. We can look at the standard declaration elaborator for inspiration.

-- from Lean.Elab.Declaration

def getTerminationHints (stx : Syntax) : TerminationHints :=
  let decl := stx[1]
  let k := decl.getKind
  if k == ``Parser.Command.def || k == ``Parser.Command.abbrev || k == ``Parser.Command.theorem || k == ``Parser.Command.instance then
    let args := decl.getArgs
    { terminationBy? := args[args.size - 2]!.getOptional?, decreasingBy? := args[args.size - 1]!.getOptional? }
  else
    {}

@[builtin_command_elab declaration]
def elabDeclaration : CommandElab := fun stx => do
  match ( liftMacroM <| expandDeclNamespace? stx) with
  | some (ns, newStx) => do
    let ns := mkIdentFrom stx ns
    let newStx  `(namespace $ns $(⟨newStx⟩) end $ns)
    withMacroExpansion stx newStx <| elabCommand newStx
  | none => do
    let decl     := stx[1]
    let declKind := decl.getKind
    if declKind == ``Lean.Parser.Command.«axiom» then
      let modifiers  elabModifiers stx[0]
      elabAxiom modifiers decl
    else if declKind == ``Lean.Parser.Command.«inductive» then
      let modifiers  elabModifiers stx[0]
      elabInductive modifiers decl
    else if declKind == ``Lean.Parser.Command.classInductive then
      let modifiers  elabModifiers stx[0]
      elabClassInductive modifiers decl
    else if declKind == ``Lean.Parser.Command.«structure» then
      let modifiers  elabModifiers stx[0]
      elabStructure modifiers decl
    else if isDefLike decl then
      elabMutualDef #[stx] (getTerminationHints stx)
    else
      throwError "unexpected declaration"

You only care about defs, so the key line there is

      elabMutualDef #[stx] (getTerminationHints stx)

You can define a new version of getTerminationHints that inserts the call to simp_wf where you want it.
With that, you define the elaborator for custom_def

@[command_elab custom_def]
def elabCustomDef : CommandElab := fun stx => do
  match ( liftMacroM <| expandDeclNamespace? stx) with
  | some (ns, newStx) => do
    let ns := mkIdentFrom stx ns
    let newStx  `(namespace $ns $(⟨newStx⟩) end $ns)
    withMacroExpansion stx newStx <| elabCommand newStx
  | none => do
    elabMutualDef #[stx] (getCustomTerminationHints stx)

I hope that helps! Feel free to ask for more clarification if the code is not clear

Mario Carneiro (Mar 17 2023 at 13:56):

There is no need to override decreasing_by (which is not extensible). You can instead override decreasing_tactic (which is extensible)

Mario Carneiro (Mar 17 2023 at 13:57):

note that decreasing_tactic calls decreasing_with which starts with a call to simp_wf

Heather Macbeth (Mar 17 2023 at 19:12):

@Alex Keizer Thanks! I learned a lot from your explanation, but I think if the scale of change needed is not just custom_decreasing_by but also custom_def then it's not worth it for me.

@Mario Carneiro I'm not sure I follow, can you explain? Note that I'm specifically interested in what is displayed in the goal state in the place marked with *****

def gcd : Nat  Nat  Nat
  | a, 0 => a
  | a, b + 1 => gcd (b + 1) (a % (b + 1))
termination_by gcd a b => b
decreasing_by ***** sorry

At that point, decreasing_tactic does not seem to be relevant? IIUC decreasing_tactic is only called as a finishing tactic if the decreasing_by line is missing. I'm proposing effectively that one of the innards of decreasing_tactic, namely simp_wf, be called as a cleanup tactic as part of a decreasing_by line.


Last updated: Dec 20 2023 at 11:08 UTC