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