simp

[simp] 属性を定理に付与すると、simp タクティクによって単純化ルールとして使用されるようになります。

/-- 標準の`Nat`を真似て自作した型 -/
inductive MyNat where
  | zero
  | succ (n : MyNat)

/-- `MyNat`上の足し算 -/
def MyNat.add (m n : MyNat) : MyNat :=
  match n with
  | .zero => m
  | .succ n => succ (add m n)

instance : Add MyNat where
  add := MyNat.add

instance : Zero MyNat where
  zero := MyNat.zero

theorem MyNat.add_zero (n : MyNat) : n + 0 = n := by
  rfl

-- 最初は`simp`で示すことができない
example (n : MyNat) : (n + 0) + 0 = n := by
  fail_if_success simp

  rw [MyNat.add_zero n]
  rw [MyNat.add_zero n]

-- `[simp]`属性を付与する
attribute [simp] MyNat.add_zero

-- `simp`で示せるようになった!
example (n : MyNat) : (n + 0) + 0 = n := by
  simp

関数に付与した場合

[simp] 属性は定理だけでなく関数定義などにも付与することができます。関数定義に付与した場合、その関数について定義からただちに従う関数等式が自動的に見出だされて simp 補題として登録されます。

/-- フィボナッチ数列 -/
def Nat.fib (n : Nat) : Nat :=
  match n with
  | 0 => 0
  | 1 => 1
  | n + 2 => Nat.fib (n + 1) + Nat.fib n

-- `rfl` ですぐ示せる関数等式があるが、`simp` では示せない
example : Nat.fib 0 = 0 := by
  fail_if_success simp
  rfl

example : Nat.fib 1 = 1 := by
  fail_if_success simp
  rfl

example (n : Nat) : Nat.fib (n + 2) = Nat.fib (n + 1) + Nat.fib n := by
  fail_if_success simp
  rfl

-- `Nat.fib` に対する `simp` 補題を生成する
attribute [simp] Nat.fib

-- `simp` で示せるようになる!
example : Nat.fib 0 = 0 := by
  simp

example : Nat.fib 1 = 1 := by
  simp

example (n : Nat) : Nat.fib (n + 2) = Nat.fib (n + 1) + Nat.fib n := by
  simp

simp 補題の優先順位

simp 補題による単純化は、おおむね「内側から外側へ」という順序で行われます。 これはオプションでsimpのトレース情報を出力させてみると確認できます。

/-- フィボナッチ数列 -/
def Nat.fib (n : Nat) : Nat :=
  match n with
  | 0 => 0
  | 1 => 1
  | n + 2 => Nat.fib (n + 1) + Nat.fib n

@[simp]
theorem Nat.fib_zero : Nat.fib 0 = 0 := by
  rfl

-- `simp`の行った書き換えを追跡する
set_option trace.Meta.Tactic.simp.rewrite true in

-- 内側から外側へという順序で単純化を行っていることがわかる。
/-
trace: [Meta.Tactic.simp.rewrite] Nat.fib_zero:1000:
      Nat.fib 0
    ==>
      0
[Meta.Tactic.simp.rewrite] Nat.add_zero:1000:
      0 + 0
    ==>
      0
[Meta.Tactic.simp.rewrite] Nat.mul_zero:1000:
      37 * 0
    ==>
      0
[Meta.Tactic.simp.rewrite] eq_self:1000:
      0 = 0
    ==>
      True
-/
example : 37 * (Nat.fib 0 + 0) = 0 := by
  simp

[simp←]

simp 補題は通常「左辺を右辺に」単純化するために使用されますが、逆方向に使用したい場合は [simp←] とします。

/-- モノイド -/
class Monoid (α : Type) extends Mul α, One α where
  mul_one : ∀ a : α, a * 1 = a
  one_mul : ∀ a : α, 1 * a = a
  mul_assoc : ∀ a b c : α, (a * b) * c = a * (b * c)

variable {α : Type} [Monoid α]

@[simp←]
theorem mul_one_rev (a : α) : a = 1 * a := by
  rw [Monoid.one_mul a]

-- `simp?` で確認してみると、該当する補題に `←` が付与されている
/-
info: Try this:
  [apply] simp only [← mul_one_rev]
-/
example (a : α) : 1 * (1 * a) = a := by
  simp?

[simp↓]

simp はデフォルトでは部分式をすべて単純化した後に全体の式に単純化を適用しています。simp タクティクに、部分式が単純化されるよりも先に前処理として単純化したいルールがある場合は、[simp↓] を使用します。

/-- 整数のような何か -/
opaque MyInt : Type

variable [LE MyInt] [Add MyInt] [Zero MyInt]

@[simp]
axiom MyInt.add_zero (a : MyInt) : a + 0 = a

@[simp]
axiom MyInt.simp_le_add (a b c : MyInt) : a + b ≤ a + c ↔ b ≤ c


example (a b : MyInt) (h : a + 0 ≤ a + b) : 0 ≤ b := by
  simp at h

  -- `simp` により `add_zero` が先に適用されてしまう
  -- 本当は `simp_le_add` が先に適用されて欲しい…
  guard_hyp h : a ≤ a + b

  sorry

-- `[simp↓]` 属性を付与する
attribute [simp↓] MyInt.simp_le_add

-- `simp`の行った書き換えを追跡する
set_option trace.Meta.Tactic.simp.rewrite true

/-
trace: [Meta.Tactic.simp.rewrite] ↓ MyInt.simp_le_add:1000:
      a + 0 ≤ a + b
    ==>
      0 ≤ b
-/
example (a b : MyInt) (h : a + 0 ≤ a + b) : 0 ≤ b := by
  -- ちゃんと `simp_le_add` が先に適用されるようになった!
  simp at h
  assumption

優先度指定

simp 補題の中でも早い段階で適用してほしい補題や、遅い段階で適用してほしい補題があるとき、優先度を指定することができます。

simp high

[simp high] とすると優先度が高まり、他の simp 補題よりも先に適用されるようになります。

/-- 謎の自然数 -/
opaque foo : Nat

/-- 謎の自然数 -/
opaque bar : Nat

/-- 謎の自然数 -/
opaque baz : Nat

@[simp]
axiom foo_eq_bar : foo = bar

@[simp]
axiom foo_eq_baz : foo = baz


example : foo = baz := by
  -- 先に宣言された方の `foo_eq_bar` が適用されてしまって、
  -- 証明することができない
  simp

  guard_target =ₛ bar = baz

  sorry

attribute [simp high] foo_eq_baz

example : foo = baz := by
  -- `foo_eq_baz` の方が優先度が高いため、こちらが先に適用される
  simp

simp low

[simp low] とすると優先度が低くなり、他の simp 補題よりも後に適用されるようになります。

/-- 謎の自然数 -/
opaque foo : Nat

/-- 謎の自然数 -/
opaque bar : Nat

/-- 謎の自然数 -/
opaque baz : Nat

@[simp]
axiom foo_eq_bar : foo = bar

@[simp]
axiom foo_eq_baz : foo = baz


example : foo = baz := by
  -- 先に宣言された方の `foo_eq_bar` が適用されてしまって、
  -- 証明することができない
  simp

  guard_target =ₛ bar = baz

  sorry

-- `foo_eq_bar` の優先度を下げる
attribute [simp low] foo_eq_bar

example : foo = baz := by
  -- `foo_eq_baz` の方が優先度が高いため、こちらが先に適用される
  simp