|
1 |
| -import Mathlib.Tactic |
| 1 | +/- # linter.flexible |
2 | 2 |
|
| 3 | +`linter.flexible` オプションは、ライブラリの変更に対して頑強な証明を作るためのリンターの有効/無効を切り替えます。 |
| 4 | +
|
| 5 | +## 背景 |
| 6 | +
|
| 7 | +証明を書く際のベストプラクティスとして、[`simp`](#{root}/Reference/Tactic/Simp.md) タクティクのような柔軟性のあるタクティクは、証明の末尾以外で使わないほうがよいということが知られています。[^non-terminal-simp] `simp` タクティクはどういう補題が `[simp]` 属性で登録されているかに応じて挙動が変わるため、Mathlib に新たな`simp` 補題が追加されたり、あるいは削除されたりするたびに挙動が変わってしまうことがその理由です。 |
| 8 | +
|
| 9 | +たとえば、以下のような証明を考えてみます。 |
| 10 | +-/ |
| 11 | +import Mathlib.Tactic --# |
| 12 | + |
| 13 | +section --# |
| 14 | +set_option linter.flexible false --# |
| 15 | + |
| 16 | +opaque foo : Nat |
| 17 | +opaque bar : Nat |
| 18 | + |
| 19 | +/-- `foo` と `bar` が等しいという定理 -/ |
| 20 | +axiom foo_eq_bar : foo = bar |
| 21 | + |
| 22 | +example {P : Nat → Prop} (hbar : P bar) : True ∧ (P foo) := by |
| 23 | + simp |
| 24 | + rw [foo_eq_bar] |
| 25 | + assumption |
| 26 | + |
| 27 | +/- ここで、定理 `foo_eq_bar` は今のところ `[simp]` 属性が与えられていませんが、与えられると `rw` タクティクが失敗するようになります。-/ |
| 28 | + |
| 29 | +attribute [simp] foo_eq_bar |
| 30 | + |
| 31 | +example {P : Nat → Prop} (hbar : P bar) : True ∧ (P foo) := by |
| 32 | + -- 元々はゴールを `P foo` に変えていた |
| 33 | + simp |
| 34 | + |
| 35 | + -- ゴールが変わってしまった |
| 36 | + guard_target =ₛ P bar |
| 37 | + |
| 38 | + -- `rw` タクティクが失敗するようになった |
| 39 | + fail_if_success rw [foo_eq_bar] |
| 40 | + |
| 41 | + assumption |
| 42 | + |
| 43 | +end --# |
| 44 | +/- ここで問題は、`[simp]` 属性が与えられる前の証明において、`simp` タクティクがゴールをどのように変えていたのかわからないということです。言い換えると、元々の証明の意図がわからないということです。それがわかっていれば、ライブラリの変更に合わせて証明を修正するのが少しは容易になります。 |
| 45 | +
|
| 46 | +これは `simp` が柔軟(flexible)なタクティクであることの弊害で、対策として次のような方法が知られています。 |
| 47 | +
|
| 48 | +* 証明末でしか `simp` タクティクを使わない。証明末であれば、「壊れる前の証明において `simp` が何を行っていたか」はつねに「残りのゴールを閉じる」であり明確で、ライブラリの変更があってもどう修正すればいいかがわかりやすいからです。 |
| 49 | +* [`have`](#{root}/Reference/Tactic/Have.md) タクティクや [`suffices`](#{root}/Reference/Tactic/Suffices.md) タクティクを使って証明末そのものを増やす。 |
| 50 | +* `simp only` 構文を使って、ライブラリ側に変更があっても `simp` タクティクの挙動が変わらないようにする。 |
| 51 | +* `simpa` タクティクのような、ゴールを閉じなければならないという制約を持つタクティクで書き換える。 |
| 52 | +-/ |
| 53 | +/- ## このリンタについて |
| 54 | +このリンタを有効にすると、上記のようなライブラリの変更に対して脆弱な証明を自動で検出して、警告を出してくれます。 |
| 55 | +-/ |
| 56 | +section --# |
| 57 | + |
| 58 | +-- flexible tactic リンタを有効にする |
3 | 59 | set_option linter.flexible true
|
4 | 60 |
|
5 |
| -example {n m : Nat} (h : n + 0 = m) : True ∧ (n = m) := by |
6 |
| - constructor |
7 |
| - all_goals |
8 |
| - simp_all |
| 61 | +/-- |
| 62 | +warning: 'simp' is a flexible tactic modifying '⊢'… |
| 63 | +note: this linter can be disabled with `set_option linter.flexible false` |
| 64 | +-/ |
| 65 | +#guard_msgs (warning) in |
| 66 | + example {n m : Nat} (h : n = m) : True ∧ (n = m) := by |
| 67 | + simp |
| 68 | + exact h |
| 69 | + |
| 70 | +example {n m : Nat} (h : n = m) : True ∧ (n = m) := by |
| 71 | + -- 書き換えると警告が消える |
| 72 | + simpa |
| 73 | + |
| 74 | +/- [^non-terminal-simp]: <https://leanprover-community.github.io/extras/simp.html#non-terminal-simps> を参照のこと。-/ |
| 75 | + |
| 76 | +end --# |
0 commit comments