Zulip Chat Archive

Stream: lean4

Topic: Elab term ensuring value


Patrick Massot (Nov 16 2023 at 17:39):

I know docs#Lean.Elab.Tactic.elabTermEnsuringType allow to elaborate a term while ensuring is has a given type (t : Expr). I need a variant where I want to elaborate a term while ensuring its value is defEq to a given expression. Does that exist?

Mario Carneiro (Nov 16 2023 at 17:40):

just elaborate it and then isDefEq the result

Patrick Massot (Nov 16 2023 at 17:41):

The defEq would come too late. Typically the term would be 0 < 1 and I want to compare it to (0 : Int) < 1.

Eric Wieser (Nov 16 2023 at 17:43):

Can you make a #mwe? Does elaborating rfl : $t = $input work?

Patrick Massot (Nov 16 2023 at 17:48):

The context is porting Lean verbose to Lean 4. There I have a version of suffices that allows to list multiple goals that have to be defEq to the goals created by apply.

import Mathlib.Tactic

open Lean Elab Tactic Meta

def bySufficesTac (fact : Term) (goals : Array Term) : TacticM Unit := do
  let mainGoal  getMainGoal
  mainGoal.withContext do
  let newGoals  mainGoal.apply ( elabTerm fact none)
  if newGoals.length != goals.size then
    throwError "Applying this leads to {newGoals.length} goals, not {goals.size}."
  let mut newerGoals : Array MVarId := #[]
  for (goal, announced) in newGoals.zip goals.toList do
    let announcedExpr  elabTermEnsuringType announced none
    newerGoals := newerGoals.push ( goal.replaceTargetDefEq announcedExpr)
  replaceMainGoal newerGoals.toList

declare_syntax_cat maybeApplied
syntax term : maybeApplied
syntax term "applied to" term : maybeApplied
syntax term "applied to [" term,* "]" : maybeApplied

def maybeAppliedToTerm : TSyntax `maybeApplied  MetaM Term
| `(maybeApplied| $e:term) => pure e
| `(maybeApplied| $e:term applied to $x:term) => `($e $x)
| `(maybeApplied| $e:term applied to [$args:term,*]) => `($e $args*)
| _ => pure Syntax.missing -- This should never happen

elab "By " e:maybeApplied "it suffices to prove " "that "? colGt arg:term : tactic => do
bySufficesTac ( maybeAppliedToTerm e) #[arg]

example (Q : Prop) (h :  n : , n > 0  Q)  : Q := by
  --suffices 1 > 0 from h 1 this
  By h applied to (1 : ) it suffices to prove (1 > 0)
  norm_num

Patrick Massot (Nov 16 2023 at 17:49):

The example at the bottom fails. Ideally it would work with no type ascription at all.

Patrick Massot (Nov 16 2023 at 17:50):

The suffices call does work (but only because there is only one auxiliary goal).

Eric Wieser (Nov 16 2023 at 17:52):

Eric Wieser said:

Does elaborating rfl : $t = $input work?

It doesn't

Eric Wieser (Nov 16 2023 at 17:54):

have : $t := (?_ : $actual_goal) seems to work

Eric Wieser (Nov 16 2023 at 17:56):

In fact, you can look at how show works, since that seems to do the right thing

Thomas Murrills (Nov 16 2023 at 17:59):

I think you’ll want to elaborate the term with mayPostpone := true (as an argument to elabTermEnsuringType) and wrap part of the code in Term.withSynthesize. The issue with using Nat instead of Int is the use of instances, and we want to postpone synthesizing an instance until after the isDefEq.

Thomas Murrills (Nov 16 2023 at 18:03):

See this thread for an example of doing exactly this kind of thing; skip to the last message(s) for some relevant code

Patrick Massot (Nov 16 2023 at 18:34):

@Thomas Murrills that seems useful but I can't find any combination of those things that actually work. Do you think you could fix my mwe?

Kyle Miller (Nov 16 2023 at 18:40):

I haven't read this whole thread carefully yet, but watch out that docs#Lean.Elab.Term.elabTermEnsuringType and docs#Lean.Elab.Tactic.elabTermEnsuringType are different functions with different properties. The latter chooses default instances for example, so an isDefEq would definitely come too late.

Patrick Massot (Nov 16 2023 at 18:44):

I had to read your message several times before I was able to spot the difference...

Patrick Massot (Nov 16 2023 at 18:45):

But switching to Lean.Elab.Term.elabTermEnsuringType doesn seem to help.

Kyle Miller (Nov 16 2023 at 18:47):

It takes some more work than just switching. Here:

import Mathlib.Tactic

open Lean Elab Tactic Meta

def bySufficesTac (fact : Term) (goals : Array Term) : TacticM Unit := do
  let mainGoal  getMainGoal
  mainGoal.withContext do
  let newGoals  mainGoal.apply ( elabTerm fact none)
  if newGoals.length != goals.size then
    throwError "Applying this leads to {newGoals.length} goals, not {goals.size}."
  let mut newerGoals : Array MVarId := #[]
  for (goal, announced) in newGoals.zip goals.toList do
    let announcedExpr  Term.withSynthesize do
      let e  Term.elabTerm announced none
      -- The `withAssignableSyntheticOpaque` is to be able to assign ?_ metavariables
      unless  withAssignableSyntheticOpaque <| isDefEq e ( goal.getType) do
        throwError "Announced term{indentD e}\nis not definitionally equal to goal{
          ""}{indentD (← goal.getType)}"
      pure e
    newerGoals := newerGoals.push ( goal.replaceTargetDefEq announcedExpr)
  replaceMainGoal newerGoals.toList

declare_syntax_cat maybeApplied
syntax term : maybeApplied
syntax term "applied to" term : maybeApplied
syntax term "applied to [" term,* "]" : maybeApplied

def maybeAppliedToTerm : TSyntax `maybeApplied  MetaM Term
| `(maybeApplied| $e:term) => pure e
| `(maybeApplied| $e:term applied to $x:term) => `($e $x)
| `(maybeApplied| $e:term applied to [$args:term,*]) => `($e $args*)
| _ => pure Syntax.missing -- This should never happen

elab "By " e:maybeApplied "it suffices to prove " "that "? colGt arg:term : tactic => do
  bySufficesTac ( maybeAppliedToTerm e) #[arg]

example (Q : Prop) (h :  n : , n > 0  Q)  : Q := by
  By h applied to (1 : ) it suffices to prove (1 > 0)
  norm_num

Kyle Miller (Nov 16 2023 at 18:48):

(withAssignableSyntheticOpaque isn't necessary for your example; and actually maybe it should be removed. When it's safe isn't so clear to me, but it can be annoying when it's missing from tactics.)

Kyle Miller (Nov 16 2023 at 18:48):

The key is that the isDefEq is happening before the extra synthesis that occurs during Term.withSynthesize

Patrick Massot (Nov 16 2023 at 18:49):

Great, thank you very much! This will probably be useful in other places as well.

Kyle Miller (Nov 16 2023 at 18:53):

I think Thomas's suggestion could be incorporated, since Tactic.elabTerm does set up different error recovery options sometimes, but I don't really know when that's used.

let e  elabTerm announced none (mayPostpone := true)

Patrick Massot (Nov 16 2023 at 18:54):

That's not the same elabTerm.

Patrick Massot (Nov 16 2023 at 18:54):

Your doesn't have that argument, right?

Kyle Miller (Nov 16 2023 at 18:58):

I know, it's different, but it calls Term.elabTerm itself, and it doesn't break your example at least. I think the point is that Tactic.elabTerm makes your tactic work better for tactics like first | ... | ..., since first wants to turn off the error->sorry feature

Kyle Miller (Nov 16 2023 at 18:58):

But maybe you want to turn off error->sorry for your own tactic. In that case, make sure to wrap Term.withoutErrToSorry, and it seems Tactic.elabTerm wouldn't give you anything.

Patrick Massot (Nov 16 2023 at 19:01):

I'm sorry (or error?) but I don't understand your latest message.

Kyle Miller (Nov 16 2023 at 19:04):

By default, elaboration errors (like type mismatches) get turned into sorry terms, but if you elaborate from within Term.withoutErrToSorry, they raise exceptions instead.

Patrick Massot (Nov 16 2023 at 19:05):

I got that, but the end of you sentence is mysterious.

Kyle Miller (Nov 16 2023 at 19:06):

I mean if you use Term.withoutErrToSorry, it looks like there's no point in using Tactic.elabTerm (mayPostpone := true) instead of Term.elabTerm. The former conditionally does Term.withoutErrToSorry if the tactic state is in no recovery mode.

Patrick Massot (Nov 16 2023 at 19:06):

Ok.

Patrick Massot (Nov 16 2023 at 19:07):

Does the order of Term.withoutErrToSorry and Term.withSynthesize matter?

Patrick Massot (Nov 16 2023 at 19:07):

I currently have:

def elabTermEnsuringValue (t : Term) (val : Expr) : TermElabM Expr :=
  Term.withSynthesize do
  Term.withoutErrToSorry do
  let e  Term.elabTerm t none
  -- The `withAssignableSyntheticOpaque` is to be able to assign ?_ metavariables
  unless  withAssignableSyntheticOpaque <| isDefEq e val do
    throwError "Given term{indentD e}\nis not definitionally equal to the expected{
      ""}{indentD val}"
  return e

Thomas Murrills (Nov 16 2023 at 19:46):

Just to clarify an earlier point, Tactic.elabTermEnsuringType only chooses default instances when mayPostpone := false, right? (Using mayPostpone := true seems to avoid that choice, but I’m on mobile.)

Kyle Miller (Nov 16 2023 at 19:47):

Yes, that's my understanding. It'll also throw errors if instance problems are not solvable, which is fine.


Last updated: Dec 20 2023 at 11:08 UTC