Zulip Chat Archive

Stream: general

Topic: Tactic to add instances from hypotheses


Violeta Hernández (Nov 26 2025 at 13:54):

In combinatorial game theory, some games are "numeric", and these games have the property that any options (games one can arrive to after a move) are also numeric. The consequence is that we sometimes have to add a lot of instances at once and in a long proof this becomes quite annoying. I want some tactic that adds all of these instances automatically, e.g.

import Mathlib.Data.Set.Defs

axiom Game : Type
axiom Options : Game  Set Game

class Numeric (x : Game) : Prop

axiom numeric_of_mem_options {x y : Game} [Numeric x] (h : y  Options x) : Numeric y

example {x y : Game} [Numeric x] (h : y  Options x) : True := by
  have := numeric_of_mem_options h -- could a tactic `numeric` do this automatically for all applicable variables?
  sorry

Violeta Hernández (Nov 26 2025 at 13:55):

This presumably would just be a tactic that executes try have := numeric_of_mem_options h for all hypotheses h. It seems like it should be easy to make a quick and dirty implementation of this, but I don't know how.

Aaron Liu (Nov 26 2025 at 14:09):

This seems like something I could write

Violeta Hernández (Nov 26 2025 at 14:09):

I probably should have started by asking you

František Silváši 🦉 (Nov 26 2025 at 14:49):

Very dirty (pretty much just does what you suggested with try have ...):

import Mathlib.Data.Set.Defs
import Lean

axiom Game : Type
axiom Options : Game  Set Game

class Numeric (x : Game) : Prop

axiom numeric_of_mem_options {x y : Game} [Numeric x] (h : y  Options x) : Numeric y

open Lean Elab Tactic in
private def instances (constants : Array Name) (goal : MVarId) : MetaM (Option MVarId) := goal.withContext do
  let mut tacStr := ""
  for h in getLCtx do
    if h.isImplementationDetail then continue
    for c in constants do
      tacStr := tacStr.append s!"(try have := {c} {h.userName});"
  let .ok tac := Parser.runParserCategory (getEnv) `tactic s!"({tacStr})" | return .none
  let ([goal], _)  runTactic goal tac | return .none
  return goal

elab "instances" "[" cs:ident,+ "]" : tactic =>
  Lean.Elab.Tactic.liftMetaTactic1 (instances <| cs.getElems.map (·.getId))

example {x y : Game} [Numeric x] (h : y  Options x) : True := by
  instances [numeric_of_mem_options]
  sorry

I made it parametric over the constants you want to try (in this case it's numeric_of_mem_options).

Violeta Hernández (Nov 26 2025 at 15:07):

Thank you very much!

Violeta Hernández (Nov 26 2025 at 15:15):

Just to make sure. You don't mind this if I go ahead and add this to the CGT repo? With credit, of course.

František Silváši 🦉 (Nov 26 2025 at 15:17):

Not at all, go ahead :).

Violeta Hernández (Nov 26 2025 at 15:48):

Hm, I think I just came across a bug

import Mathlib.Data.Set.Defs
import Lean

axiom Game : Type
axiom Options : Game  Set Game

class Numeric (x : Game) : Prop

axiom numeric_of_mem_options {x y : Game} [Numeric x] (h : y  Options x) : Numeric y

open Lean Elab Tactic in
private def instances (constants : Array Name) (goal : MVarId) : MetaM (Option MVarId) := goal.withContext do
  let mut tacStr := ""
  for h in getLCtx do
    if h.isImplementationDetail then continue
    for c in constants do
      tacStr := tacStr.append s!"(try have := {c} {h.userName});"
  let .ok tac := Parser.runParserCategory (getEnv) `tactic s!"({tacStr})" | return .none
  let ([goal], _)  runTactic goal tac | return .none
  return goal

elab "instances" "[" cs:ident,+ "]" : tactic =>
  Lean.Elab.Tactic.liftMetaTactic1 (instances <| cs.getElems.map (·.getId))

-- (kernel) declaration has metavariables '_example'
example {x y z : Game} [Numeric x] (h : y  Options x) : Numeric z := by
  instances [numeric_of_mem_options]

Violeta Hernández (Nov 26 2025 at 15:49):

The tactic shouldn't really be closing the goal

Violeta Hernández (Nov 26 2025 at 15:51):

This generally seems to happen when the goal has the shape Numeric _

František Silváši 🦉 (Nov 26 2025 at 15:51):

It doesn't close the goal for me :thinking:?
I copy-pasted the whole thing in. The state is:

x y z : Game
inst : Numeric x
h : y  Options x
this : Numeric y
 Numeric z

On leanprover/lean4:v4.26.0-rc2.

František Silváši 🦉 (Nov 26 2025 at 15:51):

What version should I try this on?

Violeta Hernández (Nov 26 2025 at 15:52):

I'm running this on Lean web

František Silváši 🦉 (Nov 26 2025 at 15:53):

I can repro this on the web version, whatever that may be. I'll have a look.

Violeta Hernández (Nov 26 2025 at 15:54):

Oddly enough the web version also seems to be Lean 4.26.0-rc2...

František Silváši 🦉 (Nov 26 2025 at 15:55):

The parser invocation fails on the web version; I am assuming this is because there's something odd with (←getEnv) in the web version, which queries the local environment.

František Silváši 🦉 (Nov 26 2025 at 15:57):

And I assume the server doesn't want me querying the local environment. I'll rewrite this in a way that doesn't parse to obtain the syntax I want, give me a sec.

Violeta Hernández (Nov 26 2025 at 16:03):

It also fails in the repo, but only in some files and not in others...

František Silváši 🦉 (Nov 26 2025 at 16:04):

I'll construct the syntax by hand to be Environment-independent :).

František Silváši 🦉 (Nov 26 2025 at 16:08):

This works for me locally and on the webview (I tossed in an extra typeclass / constant) - the behaviour is _slightly_ changed because the names of the instances are inaccessible by default here.

import Mathlib.Data.Set.Defs
import Lean

axiom Game : Type
axiom Options : Game  Set Game

class Numeric (x : Game) : Prop

class Numeric' (x : Game) : Prop

axiom numeric_of_mem_options {x y : Game} [Numeric x] (h : y  Options x) : Numeric y

axiom numeric_of_mem_options' {x y : Game} [Numeric x] (h : y  Options x) : Numeric' y

open Lean Elab Tactic in
private def instances (constants : Array Name) (goal : MVarId) : MetaM (Option MVarId) := goal.withContext do
  let mut goal := goal
  for h in getLCtx do
    if h.isImplementationDetail then continue
    for c in constants do
      let ([goal'], _)  runTactic goal (←`(tactic | try have := $(mkIdent c) $(mkIdent h.userName)))
        | logError "Applying {h.userName} to {c} must yield a single goal."; return .none
      goal := goal'
  return goal

elab "instances" "[" cs:ident,+ "]" : tactic =>
  Lean.Elab.Tactic.liftMetaTactic1 (instances <| cs.getElems.map (·.getId))

example {x y z : Game} [Numeric x] (h : y  Options x) : Numeric z := by
  instances [numeric_of_mem_options, numeric_of_mem_options']

Violeta Hernández (Nov 26 2025 at 16:11):

Well, that somehow fixed things... Thanks!

František Silváši 🦉 (Nov 26 2025 at 16:13):

Will not fail silently now in case something goes wrong again at the very least :sweat_smile:.

Violeta Hernández (Nov 26 2025 at 16:33):

CGT#253 :slight_smile:

Aaron Liu (Nov 26 2025 at 17:45):

I have a proposed implementation

Violeta Hernández (Nov 26 2025 at 18:20):

Aaron Liu said:

I have a proposed implementation

Different from the implementation we already have?

Aaron Liu (Nov 26 2025 at 18:59):

it's not the same

Aaron Liu (Nov 26 2025 at 18:59):

I would argue it's better since you don't need to involve the elaborator

Jovan Gerbscheid (Nov 26 2025 at 19:03):

I think my proposed class abbrev would subsume this.
Sorry, I read it too fast

Jovan Gerbscheid (Nov 26 2025 at 19:04):

(deleted)

Aaron Liu (Nov 27 2025 at 22:04):

here it is

import Lean.Meta.Tactic.Assert
import Lean.Elab.Tactic.Meta

open Lean Meta Elab Tactic

def instances (constants : Array Name) (goal : MVarId) : MetaM (Option MVarId) := goal.withContext do
  let mut goal := goal
  for h in  getLCtx do
    if h.isImplementationDetail then continue
    ⟨_, goal  goal.assertHypotheses =<< constants.filterMapM fun c => do
      let hc  try mkAppM c #[h.toExpr] catch _ => return none
      return some {
        userName :=  mkFreshUserName `inst
        type :=  inferType hc
        value := hc
      }
  return goal

Violeta Hernández (Nov 27 2025 at 22:05):

What's the difference between this and the implementation(s) above?

Aaron Liu (Nov 27 2025 at 22:10):

doesn't use the elaborator

Aaron Liu (Nov 27 2025 at 22:19):

no runTactic

Violeta Hernández (Nov 27 2025 at 22:24):

put your impl in the PR

Aaron Liu (Nov 27 2025 at 22:26):

I hope it works (haven't tested it)

Violeta Hernández (Nov 27 2025 at 22:31):

It builds, so I guess it works


Last updated: Dec 20 2025 at 21:32 UTC