dsimproc

dsimproc は、simproc を宣言するためのコマンドの一つです。

simproc は、ある式 expr にマッチする部分を見つけたときに、より単純な式 result を動的に計算し、expr = result の証明も同時に構成するような、simp タクティクから呼び出される書き換え規則のことです。

dsimproc は simproc を定義するものですが、定義的等価(definitionally equal) な変形だけを行います。 したがって、expr = result の証明が不要です。(rfl を使うだけで済むので)

以下は、具体的な数値に対する関数呼び出しを計算して単純化するようなシンプルな simproc を定義する例です。

import Lean

def Nat.fib (n : Nat) : Nat :=
  match n with
  | 0 => 0
  | 1 => 1
  | n + 2 => fib (n + 1) + fib n

example (P : Nat → Prop) (h : P (Nat.fib 5)) : P 5 := by
  -- 最初は simp が使えない
  fail_if_success simp at h

  have : Nat.fib 5 = 5 := by rfl
  simp [this] at h
  assumption

dsimproc computeFib (Nat.fib _) := fun e => do
  -- パターンマッチ。`e : Expr` が `Nat.fib n` の形であることを確認する
  -- そうでなければ即終了する
  let_expr Nat.fib m ← e
    | return .continue

  -- `m` が自然数リテラルであることを確認する
  -- もしそうでなければ即終了する
  let some n := m.nat?
    | return .continue

  -- `Nat.fib n` を評価する
  let l := Nat.fib n

  return .visit <| Lean.toExpr l

-- 単純化をやってくれる
example (P : Nat → Prop) (h : P (Nat.fib 5)) : P 5 := by
  dsimp at h
  assumption

example (P : Nat → Prop) (h : P (Nat.fib 5)) : P 5 := by
  -- `simp_all`からも呼び出される
  simp_all

用途

simp 補題による書き換えでは無数に多くの補題を必要としそうな場合であっても、simproc を使うことで綺麗に解決できることがあります。1

open Lean Meta Qq in

/-- 数値リテラル `n` を `1 + 1 + ⋯ + 1` という形に展開する

**注意**: このsimprocが常に適用されると困るので、実際には`dsimproc_decl`を使った方が良い。
-/
dsimproc unfoldNat ((_)) := fun e => do
  -- 自然数リテラルでなければ即終了する
  let_expr OfNat.ofNat _ num inst := e
    | return .continue
  let some n := num.rawNatLit?
    | return .continue

  -- その自然数リテラルが自然数を表しているのでなければ即終了する
  let num : Q(Nat) := num
  unless ← isDefEq inst q(instOfNatNat $num) do
    return .continue

  -- `1 + 1 + ⋯ + 1` の形に展開する
  let mut result : Q(Nat) := q(1)
  let mut n := n
  while n > 1 do
    result := q($result + 1)
    n := n - 1
  return .done result

example (a : Nat) : 3 * a = 1 * a + 2 * a := by
  dsimp only [unfoldNat]
  guard_target =ₛ (1 + 1 + 1) * a = 1 * a + (1 + 1) * a
  grind

example (a : Nat) : (2 : Int) * a = a + a := by
  -- 自然数を表す自然数リテラルは存在しないので失敗する
  fail_if_success dsimp only [unfoldNat]
  grind
1

このコード例は、Robin Arnez さんに教えていただいたものを参考にしています。