Skip to content

Commit b9ecb4c

Browse files
committed
linter.flexible を紹介する
Fixes #941
1 parent 767eb4c commit b9ecb4c

File tree

3 files changed

+75
-7
lines changed

3 files changed

+75
-7
lines changed

.github/workflows/ci.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ jobs:
2525
- name: lean action
2626
uses: leanprover/lean-action@v1
2727
with:
28-
# 警告がないことを確かめる
29-
build-args: "--fail-level=warning"
28+
build-args: "--log-level=error"
3029

3130
# Lean の更新により Windows 環境でだけビルドが壊れる可能性もあるので、
3231
# 念のために Windows 環境でもビルドを行う
+73-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,76 @@
1-
import Mathlib.Tactic
1+
/- # linter.flexible
22
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 リンタを有効にする
359
set_option linter.flexible true
460

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 --#

booksrc/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575
- [オプション](./Reference/Option/README.md)
7676
- [autoImplicit: 自動束縛暗黙引数](./Reference/Option/AutoImplicit.md)
77+
- [flexible: 脆弱な証明を指摘するリンタ](./Reference/Option/Flexible.md)
7778
- [hygiene: マクロ衛生](./Reference/Option/Hygiene.md)
7879

7980
- [型クラス](./Reference/TypeClass/README.md)

0 commit comments

Comments
 (0)