Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Inspecting function induction recursors in Lean 4


Zihao Zhang (Dec 26 2025 at 10:28):

import Lean
namespace tmp


-- !benchmark @start code_aux
def double_array_elements_aux (s_old s : Array Int) (i : Nat) : Array Int :=
  if i < s.size then
    let new_s := s.set! i (2 * (s_old[i]!))
    double_array_elements_aux s_old new_s (i + 1)
  else
    s
-- !benchmark @end code_aux


def double_array_elements (s : Array Int)  : Array Int :=
  double_array_elements_aux s s 0

#check double_array_elements_aux.induct

open Lean Parser Parser.Tactic Elab Command Elab.Tactic Meta
elab "parseInduct" : tactic => do
  let env  getEnv
  env.constants.forM fun name _ => do
    logInfo s!"Constant: {name}"

elab "funInduct" : tactic => do
  let goal  getMainGoal
  let goalType  goal.getType
  -- #check tmp.double_array_elements_aux.induct
  -- Locate a call to double_array_elements_aux in the goal
  let some call := goalType.find? (fun e =>
    match e.getAppFn with
    | .const `tmp.double_array_elements_aux _ => true
    | _ => false
  ) | throwError "No double_array_elements_aux call found"

  let args := call.getAppArgs
  if args.size != 3 then throwError "Expected 3 arguments"

  -- Extract arguments
  let arg0 := args[0]!  -- s_old (fixed parameter)
  let arg1 := args[1]!  -- s (induction parameter)
  let arg2 := args[2]!  -- j (induction parameter)

  if !arg0.isFVar || !arg1.isFVar || !arg2.isFVar then
    throwError "Arguments must be fvars"

  goal.withContext do
    let name0  arg0.fvarId!.getUserName
    let name1  arg1.fvarId!.getUserName
    let name2  arg2.fvarId!.getUserName

    -- Experiment: flexibly handle an arbitrary number of fixed
    -- parameters and induction parameters
    let fixedNames : Array Name := #[name0]          -- list of fixed parameters
    let inductNames : Array Name := #[name1, name2] -- list of induction parameters
    let inductIdent := mkIdent `tmp.double_array_elements_aux.induct

    -- Approach: dynamically construct and parse a tactic string
    -- (most flexible, can handle arbitrary arity)
    let inductStr := String.intercalate ", " (inductNames.map toString).toList
    let fixedStr := String.intercalate " " (fixedNames.map toString).toList
    let tacticStr := s!"induction {inductStr} using {inductIdent.getId} {fixedStr}"

    -- Parse the tactic string using the tactic parser
    let env  getEnv
    let parserFn := Parser.runParserCategory env `tactic tacticStr
    match parserFn with
    | Except.ok stx => evalTactic stx
    | Except.error err => throwError "Failed to parse tactic: {err}"

example : 1 + 1 = 2 := by
  parseInduct

end tmp

I want to inspect .induct (e.g. double_array_elements_aux.induct) to determine the number of fixed parameters (s_old) and induction parameters (s and i), so that I can make the funInduct tactic more general. However, I found that this .induct constant does not appear in the environment, which is strange.

Aaron Liu (Dec 26 2025 at 11:03):

It will appear after the first time you use it

Robin Arnez (Dec 26 2025 at 12:05):

You'll need to use Lean.executeReservedNameAction `tmp.double_array_elements_aux.induct to make it appear

Zihao Zhang (Dec 29 2025 at 02:09):

Thank you all! I solved it.


Last updated: Feb 28 2026 at 14:05 UTC