Slide 1

Slide 1 text

国分 佑樹 Kuniwak

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

3 Go を書く際に 定理証明支援系 を使うと便利です Go に限らず、いつか必要になった時のために 定理証明支援系 を思い出してほしい (今はわからずとも必要なときにキーワードから調べられるように)

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5 Go 言語の slice の capacity が十分かどうか判定する 判定には定理証明支援系の Isabelle1 を使う 1 https://isabelle.in.tum.de/

Slide 6

Slide 6 text

6 Go 言語の slice の capacity が十分かどうか判定する 判定には定理証明支援系の Isabelle1 を使う 1 https://isabelle.in.tum.de/ slice の capacity

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

8 要素を代入 要素を参照 slice を確保

Slide 9

Slide 9 text

9 要素を代入 要素を参照 slice を確保 3 length を指定

Slide 10

Slide 10 text

10 length を 超えたアクセス length は 3

Slide 11

Slide 11 text

11 length が 3 なので 0, 1, 2 へアクセスできる 範囲外へのアクセスは panic になる

Slide 12

Slide 12 text

12 panic にならない length を拡張 length は 0

Slide 13

Slide 13 text

13

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

15 slice は連続したメモリ領域を要求する slice のメモリ領域が足らなくなるとより 大きな容量の 新しい領域が確保 される そしてこの新しい領域へ今までの要素が すべて コピー される

Slide 16

Slide 16 text

16 そして length 指定が 0 なら、最初は 小さなメモリ領域が確保される ここで何度も append を実行すれば 何度も メモリの再確保 と コピー が走る これはそこそこ重い処理なので避けたい

Slide 17

Slide 17 text

17 length は 0 だが、はじめから 3 要素分の capacity を確保 容量は十分なので メモリの再確保は 走らない

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

19 2 無駄なメモリ確保なうえに、GC のコストが高くなりこれがプログラムに よってはボトルネックになるほど遅くなることがある(あった)

Slide 20

Slide 20 text

20 3 Go 言語にユーザー定義可能な総称型がくるまでは、都度 list.List の 要素の型を固定した wrapper を書かないと型の取り違えに気づけない

Slide 21

Slide 21 text

21 ● length を超えてアクセスすると panic ● 不十分な capacity で都度 append は 遅い ● 巨大 capacity の事前確保は 無駄 → 必要最小限を一度に確保 がベスト!

Slide 22

Slide 22 text

22 いつでもぴったりの capacity が直感的に わかるとは限らない その場合、次のような手段で capacity の 十分さの仮説を検証することになる

Slide 23

Slide 23 text

23

Slide 24

Slide 24 text

24 1. 少数のサンプルで試す 2. 機械生成された大量のサンプルで試す 3. 形式検証する

Slide 25

Slide 25 text

25 1. 少数のサンプルで試す 2. 機械生成された大量のサンプルで試す 3. 形式検証する

Slide 26

Slide 26 text

26 従来の自動テストを活用して、capacity を 減らしても panic しないことを確認する 従来の知識だけで実施可能 サンプルが少ないので見逃しがあるかも

Slide 27

Slide 27 text

27 1. 少数のサンプルで試す 2. 機械生成された大量のサンプルで試す 3. 形式検証する

Slide 28

Slide 28 text

28 testing/quick パッケージ4 などによる機械生成の 大量のサンプルで panic しないことを確認する 従来の知識の延長線上なので習得容易 網羅的ではないのでまだ見逃しがあるかも 4 https://golang.org/pkg/testing/quick/

Slide 29

Slide 29 text

29 1. 少数のサンプルで試す 2. 機械生成された大量のサンプルで試す 3. 形式検証する

Slide 30

Slide 30 text

30 数理論理学の武器を駆使して capacity の十分さを 証明 する 入力を網羅した検証が可能(後述) 形式検証の知識が必要(勉強すればできます)

Slide 31

Slide 31 text

31 数理論理学の武器を駆使して capacity の十分さを 証明 する 入力を網羅した検証が可能(後述) 形式検証の知識が必要(勉強すればできます)

Slide 32

Slide 32 text

32 この 証明 とは数学での証明のこと 証明 は今まで脳内でやっていた プログラムの検証を厳密にしたもの

Slide 33

Slide 33 text

33 これから形式検証における 証明 の流れを説明していく 一度に説明すると大変なので 順を追って説明していく

Slide 34

Slide 34 text

34 なお本来は capacity が十分かつ 最小と検証できたら素晴らしいが、 以降の説明が難しくなってしまう そのため以降では capacity が 十分であることだけに着目する

Slide 35

Slide 35 text

35 以降の例は capacity が足らないとpanic して 気付けるように length 指定とインデックス アクセスにしている そのため capacity 指定と append を使っていないが、 実際のコードではたやすく panic されては困る そのため、length 指定とインデックスアクセスを capacity 指定/append に読み替えてほしい

Slide 36

Slide 36 text

36 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 37

Slide 37 text

37 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 38

Slide 38 text

38 引数に与えられたslice と同じ 内容の新しいslice を作成して返す なお i, x := range xs の方が 読みやすいが、説明の しやすさのために崩している

Slide 39

Slide 39 text

39 この length は勘で決めたので、 これで十分かどうか検証したい ちなみに capacity を省略しているが、 この場合は length と同じ値になる 今回は十分な length であれば十分な capacity といえる

Slide 40

Slide 40 text

40 先ほどの length の指定にあたって、 panic しうる危ない部分がここ どんな場合でもここで i < ys の長さ にならないといけない これを検証してみよう

Slide 41

Slide 41 text

41 今回は勘で ys の長さを len(xs) にしているので、検証したいこと である i < ys の長さ は i < len(xs) と言い換えられる

Slide 42

Slide 42 text

42 現状をまとめると、i < len(xs) が なりたつと検証できれば ys[i] の 部分では panic しないといえる 検証したいことがわかったので 検証に使える材料を集めてこよう

Slide 43

Slide 43 text

43 ループの回っている間は この 条件式 i < len(xs) が なりたっていることに注目

Slide 44

Slide 44 text

44 そして ys[i] の代入の 直前までに i は変更 されていない つまり、ys[i] の直前でも i < len(xs) がなりたつ

Slide 45

Slide 45 text

45 これまでの事実をまとめると: ys[i] の直前で i < len(xs) が なりたっている 検証したいのは ys[i] の直前で i < len(xs) がなりたっていること この上から下を証明してみよう

Slide 46

Slide 46 text

46 これまでの事実をまとめると: ys[i] の直前で i < len(xs) が なりたっている 検証したいのは ys[i] の直前で i < len(xs) がなりたっていること この上から下を証明してみよう 前提より明らか i < len(xs) i < len(xs)

Slide 47

Slide 47 text

47 こうして ys の length が len(xs) で十分であると いえる(証明終わり)

Slide 48

Slide 48 text

48 capacity の検証から脱線するが 同様の考え方で xs[i] の方でも panic を起こさないと検証できる

Slide 49

Slide 49 text

49 これまでの証明の過程で具体的な i や xs の 値を与えなかったことを思い出してほしい この証明は具体的な i や xs の値に依存して いないので、どんな i や xs でも当てはまる これが入力のサンプルを必要とする他の 検証方法にはない最大の利点

Slide 50

Slide 50 text

50 簡単な例で検証の流れを確認できた 現実の例はもっと難しいことがある もう少し難しい例をみてみよう

Slide 51

Slide 51 text

51 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 52

Slide 52 text

52 前の簡単な例の関数と似ているが ys へアクセスするインデックスの 変化が複雑な関数

Slide 53

Slide 53 text

53 今回検証したい slice は ys length をまだ決めていないので 以降の処理の流れを見た上で 推測する

Slide 54

Slide 54 text

54 ys の length の設定次第で panic しうるのがこの部分 代入先インデックスの j は ループカウンタ i がループ ごとに加算されていく

Slide 55

Slide 55 text

55 ループ n 回目 j の値 0 0 1 1 2 3 3 6 破線時点のループ n 回目の j の値は次のようになる: ループ n 回目

Slide 56

Slide 56 text

56 ループ n 回目 j の値 0 0 1 1 2 3 3 6 ループ n 回目の j は 0 から n までの数の合計と 同じになっている ループ n 回目

Slide 57

Slide 57 text

57 このような 0 から n までの 数の合計 S(n) は次のように あらわせる: n 回目のループの j の値も この式で次のようにあらわせる S(n) = n (n + 1) 2 ループ n 回目

Slide 58

Slide 58 text

58 n 回目のループの j の値 v (n) は 先ほどの S(n) を利用すると: j v (n) = n (n + 1) 2 j この時点の j の値が v (n) j ループ n 回目

Slide 59

Slide 59 text

59 j を v (n) であらわせた ただし ys の length をまだ 未設定だったので話を戻す j

Slide 60

Slide 60 text

60 これまでの推測からこの length 指定を仮で決める

Slide 61

Slide 61 text

61 ループ回数は len(xs) より小さい ので雑に length の指定に S(n) を 当てはめてみた(これは後にもう 少し改善する) ただし雑に当てはめただけなので 問題がないかどうか検証していく

Slide 62

Slide 62 text

62 この ys[j] が panic しないためには、 どんな場合でも v (n) が ys の 長さより小さい必要がある つまり検証したいことは次のとおり: v (n) < len(xs)*(len(xs) + 1)/2 ※ Go の int の除算は 0 に近い方へ丸められるが x(x+1) の形は常に偶数なので丸めは発生しない この時点の j の値が v (n) j ループ n 回目 j j

Slide 63

Slide 63 text

63 検証したいことがわかったので コードから検証の材料を集めよう

Slide 64

Slide 64 text

64 ys[j] の代入の直前で i は更新されて いないので、ys[j] の代入の直前で i < len(xs) がなりたつ(材料1) この時点では i < len(xs) が成立 ループ

Slide 65

Slide 65 text

65 ループ条件評価直前でループ回数 n と ループカウンタ i は等しい(材料2) ループ n 回目 この時点で n = i が成立

Slide 66

Slide 66 text

66 前の方で v (n) は次のように あらわせることわかった: j v (n) = (材料3) n (n + 1) 2 j この時点の j の値が v (n) j ループ n 回目

Slide 67

Slide 67 text

67 これまでに3つの材料が集まった: 材料1:i < len(xs) がなりたつ 材料2:ループ回数 n とループ カウンタ i は等しい これらの材料から以下を証明しよう: v (n) < len(xs)*(len(xs) + 1)/2 材料3:v (n) = n (n + 1) 2 j j

Slide 68

Slide 68 text

68 材料1:i < len(xs) がなりたつ 材料2:ループ回数 n とループ カウンタ i は等しい v (n) < len(xs)*(len(xs) + 1)/2 材料3:v (n) = n (n + 1) 2 j これらを整理すると、 n < len(xs) がなりたつもとで 以下を証明すればよい: ここまでくればあとは 紙の上でも証明できる n (n + 1) 2 < len(xs) (len(xs) + 1) 2 j

Slide 69

Slide 69 text

69 簡単な例と同じ考え方で slice へのインデックス アクセスで panic しないことを証明できた ただしこれは紙の上の証明なので間違っている かもしれない 間違いを含んだ証明では正しい検証に ならないので 間違わない工夫 が必要になる

Slide 70

Slide 70 text

70 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 71

Slide 71 text

71 定理証明支援系はその名のとおり数学の 証明を以下の 2 つの方向から支援する: 1. 証明を検査してくれる ○ 検査された証明は正しいとわかる5 2. 証明を一部手伝ってくれる ○ 場合によっては全自動で証明できる 5 ここでは定理証明支援系の正しさを仮定している

Slide 72

Slide 72 text

72 6 https://isabelle.in.tum.de/

Slide 73

Slide 73 text

73 ここから Isabelle のコードが登場するが、 時間の都合上詳しくコードを解説できない ここでは Isabelle というツールで証明を できて、かつ便利だと伝えたいだけなので 丁寧に追わなくとも問題ない

Slide 74

Slide 74 text

74 v (n) の定義 j これから今回の例を Isabelle 上で証明していく まず準備として証明に必要となる v (n) を定義する: j

Slide 75

Slide 75 text

75 これから今回の例を Isabelle 上で証明していく まず準備として証明に必要となる v (n) を定義する: j v (n) の定義 j ループ回数

Slide 76

Slide 76 text

76 これから今回の例を Isabelle 上で証明していく まず準備として証明に必要となる v (n) を定義する: j ループ回数 v (n) の定義 j v (n) = n (n + 1) 2 j

Slide 77

Slide 77 text

77 ⟹ 証明 証明したいこと

Slide 78

Slide 78 text

78 ⟹ 証明 証明したいこと これまでの証明したいことをそのままここに記述する 記述部分は前提と帰結に分けて書いている ⟹

Slide 79

Slide 79 text

79 ⟹ 証明 前提 先ほど集めてきた材料を 前提側にいれる v_j は先ほど定義したので 暗黙的に前提になっている 材料1 材料2

Slide 80

Slide 80 text

80 証明 ⟹ 帰結 前提のもとで証明したかったことを帰結にいれる ⟹

Slide 81

Slide 81 text

81 ⟹ 証明したいこと 証明 証明したいことを定義できたので 証明する手順を指示していく

Slide 82

Slide 82 text

82 証明したいこと ⟹ 証明 Isabelle の機能に自動証明があるので利用する

Slide 83

Slide 83 text

83 証明したいこと ⟹ 証明 ここの部分には別の Isabelle の機能が自動で 見つけてきたヒントを指定している

Slide 84

Slide 84 text

84 証明したいこと ⟹ 証明 自動証明だけで証明できたので証明を完了する なお証明が完了していないと done でエラーに なるのでわかる

Slide 85

Slide 85 text

85 ● 複雑なコードになるにつれ証明を 間違う可能性が高くなってきた ● Isabelle で証明を検査することで 証明の間違いを防いだ ● かつ Isabelle の自動証明によって 手軽に証明を完了できた

Slide 86

Slide 86 text

86 先ほどは雑な推測により length を決めたが 別のより小さい length も試してみたい より小さい

Slide 87

Slide 87 text

87 ⟹ 証明 証明したいこと

Slide 88

Slide 88 text

88 ⟹ 証明 証明したいこと ⟹ より小さい length に書き換えたものを追加して証明を再開

Slide 89

Slide 89 text

89 ⟹ 証明したいこと 証明 たくさん書いてあるので大変そうに見えるが、実際は全部 Isabelle が自動で見つけてきて証明してくれた

Slide 90

Slide 90 text

90 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 91

Slide 91 text

91 これまでの検証では slice 内の要素に capacity が依存しない例を扱ってきた 実際のプログラムでは slice 内の要素に 応じて必要な capacity が変わりうる このようなプログラムも検証してみよう

Slide 92

Slide 92 text

92 先頭は内部の slice の長さ slice が平たく埋め込まれている 先頭の数値をみながら 分割した slice を返す 入力の長さが一定なら 埋め込まれた slice が 長いほど荒く分割されて 外側の slice は短くなる

Slide 93

Slide 93 text

93

Slide 94

Slide 94 text

94 今回も結果の slice の length を検証する

Slide 95

Slide 95 text

95 今回の length 設定で危ない部分はここ

Slide 96

Slide 96 text

96 このインデックスはループカウンタの i になっている つまり xss の length はループ回数より大きい必要がある ループ

Slide 97

Slide 97 text

97 xss の length がループ回数より 大きいことを検証する

Slide 98

Slide 98 text

98 注意が必要なのは、ループ条件にループカウンタ i が 使われていないのでループ回数がわかりづらいということ ここで読んだ値が大きいほど j が大きく増加するので 少ないループ回数でループ条件を満たさなくなる

Slide 99

Slide 99 text

99 まずループ条件評価直前の j の値をループ回数 n と xs であらわそう ループ n 回目 ループ条件評価直前の j の値を v (n, xs) とする j するとループ条件 v (n, xs) < len(xs) をはじめて 偽にする最小の n がループ終了までの回数になる j

Slide 100

Slide 100 text

100 ループ条件評価直前の j の値を v (n, xs) とする j ループ n 回目 するとループ条件 v (n, xs) < len(xs) をはじめて 偽にする最小の n がループ終了までの回数になる j ループ条件 v (n, xs) < len(xs) をはじめて 偽にする最小の n j

Slide 101

Slide 101 text

101 7 実はこのループ条件の考え方は P.64 でも使っていた ループ条件 v (n, xs) < len(xs) をはじめて偽にする 「最小の n」がループ終了までの回数になる この「最小の n」を N とおくと、調べるべき ループ回数の範囲は 0 以上 N 未満となる v (n, xs) が複雑なので N の値を具体的には求められないが、 条件 v (n, xs) < len(xs) がなりたつとすれば 7、 n の範囲を 0 以上 N 未満と制限したときと同じことになる j j j

Slide 102

Slide 102 text

102 実際に v (n, xs) の表現を考えてみる k の宣言時点では前回のループの j の値を参照している j 前回のループの j の値 ループ条件評価直前の j の値を v (n, xs) とする j ループ n 回目

Slide 103

Slide 103 text

103 j ループ n 回目 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j 前のループの j の値 前のループの j の値 ループ条件評価直前の j の値を v (n, xs) とする v (n, xs) は前のループのときの値を含んでいるので漸化式になる j

Slide 104

Slide 104 text

104 v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j 前のループの j の値 前のループの j の値 ループ n 回目 0 (n = 0) v (n, xs) = j ループ条件評価直前の j の値を v (n, xs) とする v (n, xs) は前のループのときの値を含んでいるので漸化式になる j v (0, xs) はループ実行前なので 0 のまま j j

Slide 105

Slide 105 text

105 0 (n = 0) ループ条件評価直前の j の値を v (n, xs) とする j ループ n 回目 v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j 前のループの j の値 前のループの j の値 v (n, xs) は前のループのときの値を含んでいるので漸化式になる j v (n, xs) は1つ前の ループ時点の値を参照 j

Slide 106

Slide 106 text

106 整理すると、上のような漸化式で v (n, xs) を表現でき、 これを使って表現した v (n, xs) < len(xs) が なりたつ範囲を検証すればよい 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j 前のループの j の値 前のループの j の値 j j

Slide 107

Slide 107 text

107 v (n, xs) についてはわかってきたので 検証したいことを再確認しよう j

Slide 108

Slide 108 text

108 xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j

Slide 109

Slide 109 text

109 Isabelle の記述 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j v (n, xs) の定義 j

Slide 110

Slide 110 text

110 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j j Isabelle の記述 v (n, xs) の定義 (n = 0) v (n, xs) = j (n > 0) Isabelle も場合ごとにわけて記述する

Slide 111

Slide 111 text

111 v (n, xs) の定義 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j j Isabelle の記述 ループ回数 入力の slice に相当するもの ループ回数(Suc n は引数に n+1 が与えられたときと考えればよい)

Slide 112

Slide 112 text

112 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j v (n, xs) の定義 j 0 (n = 0) v (n, xs) = j Isabelle の記述 引数が 0 の場合

Slide 113

Slide 113 text

113 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j v (n, xs) の定義 j v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j Isabelle の記述 引数が n +1 の場合

Slide 114

Slide 114 text

114 Isabelle の記述 0 (n = 0) v (n, xs) = j v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j v (n, xs) の定義 j v (n, xs) を定義できたので 証明したいことの記述に移ろう j

Slide 115

Slide 115 text

115 ⟹ 証明したいこと 証明 証明したいこと xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j

Slide 116

Slide 116 text

116 証明したいこと xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j 証明 ⟹ 証明したいこと ⟹ xss の length がループ回数より 大きいことを検証したい

Slide 117

Slide 117 text

117 ⟹ 証明したいこと xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j 証明 証明したいこと ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j

Slide 118

Slide 118 text

118 証明したいこと 証明 ⟹ 証明したいこと ⟹ この形をよくみると、 n ≦ v_j n xs がなりたてば、前提の ② v_j n xs < length xs から ③ がいえる: n < length xs ② ③ ①

Slide 119

Slide 119 text

119 証明したいこと ① 証明したいこと ⟹ ② 証明の形をよくみると、 n ≦ v_j n xs 証明 n ≦ v_j n xs この意味を考えると、 v (n, xs) の値は常に n 以上であるということ 漸化式をみるとループごと に少なくとも +1 されて いるのでなりたちそう v (n - 1, xs) + 1 + xs[v (n - 1, xs)] (n > 0) j j ループごとに j の値は少なくとも元の値より +1 される ① j

Slide 120

Slide 120 text

120 証明したいこと ① 証明したいこと ② 証明の形をよくみると、 n ≦ v_j n xs 証明 ⟹ n ≦ v_j n xs 先にこれを証明して後の 証明を楽にしておこう このように本体の証明から 切り出した部分を 補題とよぶ

Slide 121

Slide 121 text

121 ⟶ 補題 証明

Slide 122

Slide 122 text

122 ⟶ 証明 補題 補題で解いておきたかったもの

Slide 123

Slide 123 text

123 ⟶ 証明 補題 ⟶ ループ条件の前提はここでもなりたつのでもってきた

Slide 124

Slide 124 text

124 ⟶ 証明 補題 この補題を証明するには工夫が必要になる ⟶ v_j を漸化式であらわしていたことを思いだそう

Slide 125

Slide 125 text

125 補題 ⟶ 証明 v_j は漸化式なので具体的な値がわかりづらい 無理やり n=k のときの具体的な v_j の値を求めても、 n=k - 1 のときの v_j の値が必要でこれを求めるには n=k - 2 のときの v_j の値が必要で、… のようにどこまでも展開できてしまう そこで 数学的帰納法 を使ってこの補題を解いてみよう

Slide 126

Slide 126 text

126 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える

Slide 127

Slide 127 text

127 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える 証明したい自然数に関する命題 P(n) ここに先ほどの補題をあてはめることを考えよう: 補題 ⟶

Slide 128

Slide 128 text

128 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ n = 0 のとき P(0) がなりたつ

Slide 129

Slide 129 text

129 まず補題の n を 0 へ書き換えた: ⟶

Slide 130

Slide 130 text

130 まず補題の n を 0 へ書き換えた: そして n=0 のときの v_j の値は 0 とわかっている: ⟶ ⟶

Slide 131

Slide 131 text

131 まず補題の n を 0 へ書き換えた: そして n=0 のときの v_j の値は 0 とわかっている: ⟶ ⟶ どんな xs が与えられても 0 ≦ 0 はなりたつので P(0) はなりたつ

Slide 132

Slide 132 text

132 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ n = 0 のとき P(0) がなりたつ なりたつと証明できた

Slide 133

Slide 133 text

133 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ

Slide 134

Slide 134 text

134 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える ⟶ ⟹ ⟶ P(k) 補題 ⟶ P(k) を前提に P(k+1) をいいたい P(k+1)

Slide 135

Slide 135 text

135 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える ⟶ ⟹ ⟶ P(k) P(k+1) k と k+1 の両方のときの v_j があらわれるので一見複雑そうに 見えるが、これを整理すると簡単な不等式にできる なぜ簡単にできるかというと、v_j は漸化式だったので k+1 のときの v_j の値を k のときの値で表現できるため

Slide 136

Slide 136 text

136 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える ⟶ ⟹ ⟶ P(k) P(k+1) そこで k のときの v_j の値だけがあらわれるように 不等式を変形すればすぐ証明できる なお、時間が足らないのでここでの証明は割愛する

Slide 137

Slide 137 text

137 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える なりたつと証明できた どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ

Slide 138

Slide 138 text

138 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える このとき、どんな n についても P(n) がなりたつ 先ほど当てはめた P(n) を思い出すと: 補題 ⟶

Slide 139

Slide 139 text

139 証明したい自然数に関する命題 P(n) について: n = 0 のとき P(0) がなりたつ8 どんな k についても、n = k のとき P(k) を 仮定すると P(k+1) もなりたつ このとき、どんな n についても P(n) がなりたつ 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える 数学的帰納法により補題を証明できた!

Slide 140

Slide 140 text

140 ⟶ 補題 証明

Slide 141

Slide 141 text

141 補題 証明 自動証明の前に n について数学的帰納法を使うよう指示  するとあっさりと解かれる 補題を証明できたのでこれを使ってもとの証明を解いてみよう

Slide 142

Slide 142 text

142 ⟹ 証明したいこと 証明 証明したいこと

Slide 143

Slide 143 text

143 ⟹ 証明したいこと 証明したいこと 証明 こちらも Isabelle に任せてみると補題を使って あっさりと証明されてしまった つまり、検証した部分では panic が起こらないことを検証できた

Slide 144

Slide 144 text

144 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

Slide 145

Slide 145 text

145 ● 形式検証は網羅的に検証できる方法 ● Isabelle は形式検証で使える強力なツール ● 問題によっては数学的帰納法を駆使した ● ほとんどは自動で capacity を検証できた

Slide 146

Slide 146 text

146 もう少しだけ続きます

Slide 147

Slide 147 text

147 これまでは capacity だけを扱ってきた 実際には形式検証の適用範囲はさらに広く プログラムの性質 を証明できる

Slide 148

Slide 148 text

148 プログラムの性質 の典型的な例: ● どんな入力も考慮されている ● デッドロックしない ● いつか結果が帰ってくる ● 結果がソートされている ● …

Slide 149

Slide 149 text

149 実は従来の自動テストの本質も 性質 の検証 ただし自動テストは簡単だが 網羅性はない

Slide 150

Slide 150 text

150 もし従来のテストで非現実的と諦めた検証が あるなら 形式検証 を試してみてほしい(例) ● どんな入力も考慮されている ● デッドロックしない ● いつか結果が帰ってくる ● 結果がソートされている ● …

Slide 151

Slide 151 text

151 あるバイナリフォーマットの 3-way merge ツールの 実装で「処理が不定となる入力が存在しない」の 性質を 形式検証 で確かめている

Slide 152

Slide 152 text

152

Slide 153

Slide 153 text

153 これまでの検証で「ループ直前では〜が なりたつ」のような Go 言語の振る舞いに 関する仮定 をたくさん使ってきた この仮定は本当に正しかったのだろうか? もし仮定に誤りがあればこれまでの検証を 正しいとはいえなくなる

Slide 154

Slide 154 text

154

Slide 155

Slide 155 text

155 Go のプログラム意味論を使えばこれまでの 証明で使った仮定の正しさを証明できる9 興味があれば「プログラム意味論の基礎10」や 「Featherweight Go11」を読もう 9 後述するが現場では厳密な方法ではない別の方法をとっている 10 https://www.saiensu.co.jp/search/?isbn=978-4-7819-1483-1&y=2020 11 https://arxiv.org/abs/2005.11710

Slide 156

Slide 156 text

156 私の現場では、少し不安が残るがプログラム 意味論を使わない別の方法をとっている この方法を Isabelle ファースト と呼んでいる Isabelle ファースト とこれの対になる Go ファースト を比較してみよう

Slide 157

Slide 157 text

157 先に Go のプログラムを書き、ここから Isabelle のプログラムと定理に書き換える 先に Isabelle のプログラムと定理を書き、 ここから Go のコードへ書き換える

Slide 158

Slide 158 text

158 12 defer とか panic とかでどういう定理がなりたつんですか!?? のように なぜか Go の機能や標準ライブラリの証明までする羽目になる 証明が難しい!Go のいろいろな事情12 に 振り回されるので大変 すでに進んでしまったプロジェクトに 後からいれやすい

Slide 159

Slide 159 text

159 証明しやすい。証明に不要な Go の事情を全部 省いて証明しやすい状態からスタートできる。 Go のコードに落とすのは難しくない defer や panic、関数ポインタなどは証明が 難しいので進んでは使えない

Slide 160

Slide 160 text

160

Slide 161

Slide 161 text

161

Slide 162

Slide 162 text

162

Slide 163

Slide 163 text

163 まとめると、テストケースが多くないと 安心できない状況が予期されるケースは 先に Isabelle で書き始めてしまう方 が 安心して開発できる

Slide 164

Slide 164 text

164 まとめると、テストケースが多くないと 安心できない状況が予期されるケースは 先に Isabelle で書き始めてしまう方 が 安心して開発できる       テストケースが多くないと 安心できない状況

Slide 165

Slide 165 text

165 うちは医療や航空宇宙、公共 インフラでないので…と思った方へ 諦めている/気づいていないだけで 身近な例もたくさんあります

Slide 166

Slide 166 text

166 実は身近にいろいろ 活躍できる場面 がある: ● 私の現場の 3-way merge ツールのような 失敗が一見わからない上に放置すると 復旧が高コストになるパターン ● 数学的にきれいに仕様を書けるが 入力パターン数が多いもの(ソートなど)

Slide 167

Slide 167 text

167

Slide 168

Slide 168 text

168 プログラムの性質を手軽に検証する方法を紹介 ● 例として Go の capacity の十分性を検証 ● 来るべき証明が必要な開発に備えて 定理証明支援系 を学んでいきましょう

Slide 169

Slide 169 text

169