Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go を書きながら定理を書く安心開発スタイル【DeNA TechCon 2021】/techcon2021-13

Go を書きながら定理を書く安心開発スタイル【DeNA TechCon 2021】/techcon2021-13

Go では slice の capacity を宣言できますが、この指定に悩む時があります。例えば直感では入力の slice より短いはず、と思っても本当にそうなのか不安な時があるでしょう。
このとき、定理証明支援系 Isabelle を開発で併用していると自分の仮説を証明しながら安心して開発できます。私の場合では仮説はほぼ自動で証明され確信をもてました。
ぜひ開発のお供に定理証明支援系はいかがでしょうか。

8a84268593355816432ceaf78777d585?s=128

DeNA_Tech

March 03, 2021
Tweet

Transcript

  1. 国分 佑樹 Kuniwak

  2. 2

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

  4. 4

  5. 5 Go 言語の slice の capacity が十分かどうか判定する 判定には定理証明支援系の Isabelle1 を使う

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

    1 https://isabelle.in.tum.de/ slice の capacity
  7. 7

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

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

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

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

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

  13. 13

  14. 14

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

    コピー される
  16. 16 そして length 指定が 0 なら、最初は 小さなメモリ領域が確保される ここで何度も append を実行すれば

    何度も メモリの再確保 と コピー が走る これはそこそこ重い処理なので避けたい
  17. 17 length は 0 だが、はじめから 3 要素分の capacity を確保 容量は十分なので

    メモリの再確保は 走らない
  18. 18

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

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

  21. 21 • length を超えてアクセスすると panic • 不十分な capacity で都度 append

    は 遅い • 巨大 capacity の事前確保は 無駄 → 必要最小限を一度に確保 がベスト!
  22. 22 いつでもぴったりの capacity が直感的に わかるとは限らない その場合、次のような手段で capacity の 十分さの仮説を検証することになる

  23. 23

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

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

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

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

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

    https://golang.org/pkg/testing/quick/
  29. 29 1. 少数のサンプルで試す 2. 機械生成された大量のサンプルで試す 3. 形式検証する

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

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

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

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

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

  35. 35 以降の例は capacity が足らないとpanic して 気付けるように length 指定とインデックス アクセスにしている そのため

    capacity 指定と append を使っていないが、 実際のコードではたやすく panic されては困る そのため、length 指定とインデックスアクセスを capacity 指定/append に読み替えてほしい
  36. 36 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

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

  38. 38 引数に与えられたslice と同じ 内容の新しいslice を作成して返す なお i, x := range

    xs の方が 読みやすいが、説明の しやすさのために崩している
  39. 39 この length は勘で決めたので、 これで十分かどうか検証したい ちなみに capacity を省略しているが、 この場合は length

    と同じ値になる 今回は十分な length であれば十分な capacity といえる
  40. 40 先ほどの length の指定にあたって、 panic しうる危ない部分がここ どんな場合でもここで i < ys

    の長さ にならないといけない これを検証してみよう
  41. 41 今回は勘で ys の長さを len(xs) にしているので、検証したいこと である i < ys

    の長さ は i < len(xs) と言い換えられる
  42. 42 現状をまとめると、i < len(xs) が なりたつと検証できれば ys[i] の 部分では panic

    しないといえる 検証したいことがわかったので 検証に使える材料を集めてこよう
  43. 43 ループの回っている間は この 条件式 i < len(xs) が なりたっていることに注目

  44. 44 そして ys[i] の代入の 直前までに i は変更 されていない つまり、ys[i] の直前でも

    i < len(xs) がなりたつ
  45. 45 これまでの事実をまとめると: ys[i] の直前で i < len(xs) が なりたっている 検証したいのは

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

    ys[i] の直前で i < len(xs) がなりたっていること この上から下を証明してみよう 前提より明らか i < len(xs) i < len(xs)
  47. 47 こうして ys の length が len(xs) で十分であると いえる(証明終わり)

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

  49. 49 これまでの証明の過程で具体的な i や xs の 値を与えなかったことを思い出してほしい この証明は具体的な i や

    xs の値に依存して いないので、どんな i や xs でも当てはまる これが入力のサンプルを必要とする他の 検証方法にはない最大の利点
  50. 50 簡単な例で検証の流れを確認できた 現実の例はもっと難しいことがある もう少し難しい例をみてみよう

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

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

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

  54. 54 ys の length の設定次第で panic しうるのがこの部分 代入先インデックスの j は

    ループカウンタ i がループ ごとに加算されていく
  55. 55 ループ n 回目 j の値 0 0 1 1

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

    2 3 3 6 ループ n 回目の j は 0 から n までの数の合計と 同じになっている ループ n 回目
  57. 57 このような 0 から n までの 数の合計 S(n) は次のように あらわせる:

    n 回目のループの j の値も この式で次のようにあらわせる S(n) = n (n + 1) 2 ループ n 回目
  58. 58 n 回目のループの j の値 v (n) は 先ほどの S(n)

    を利用すると: j v (n) = n (n + 1) 2 j この時点の j の値が v (n) j ループ n 回目
  59. 59 j を v (n) であらわせた ただし ys の length

    をまだ 未設定だったので話を戻す j
  60. 60 これまでの推測からこの length 指定を仮で決める

  61. 61 ループ回数は len(xs) より小さい ので雑に length の指定に S(n) を 当てはめてみた(これは後にもう

    少し改善する) ただし雑に当てはめただけなので 問題がないかどうか検証していく
  62. 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
  63. 63 検証したいことがわかったので コードから検証の材料を集めよう

  64. 64 ys[j] の代入の直前で i は更新されて いないので、ys[j] の代入の直前で i < len(xs)

    がなりたつ(材料1) この時点では i < len(xs) が成立 ループ
  65. 65 ループ条件評価直前でループ回数 n と ループカウンタ i は等しい(材料2) ループ n 回目

    この時点で n = i が成立
  66. 66 前の方で v (n) は次のように あらわせることわかった: j v (n) =

    (材料3) n (n + 1) 2 j この時点の j の値が v (n) j ループ n 回目
  67. 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
  68. 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
  69. 69 簡単な例と同じ考え方で slice へのインデックス アクセスで panic しないことを証明できた ただしこれは紙の上の証明なので間違っている かもしれない 間違いを含んだ証明では正しい検証に

    ならないので 間違わない工夫 が必要になる
  70. 70 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

  71. 71 定理証明支援系はその名のとおり数学の 証明を以下の 2 つの方向から支援する: 1. 証明を検査してくれる ◦ 検査された証明は正しいとわかる5 2.

    証明を一部手伝ってくれる ◦ 場合によっては全自動で証明できる 5 ここでは定理証明支援系の正しさを仮定している
  72. 72 6 https://isabelle.in.tum.de/

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

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

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

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

    v (n) の定義 j v (n) = n (n + 1) 2 j
  77. 77 ⟹ 証明 証明したいこと

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

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

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

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

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

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

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

  85. 85 • 複雑なコードになるにつれ証明を 間違う可能性が高くなってきた • Isabelle で証明を検査することで 証明の間違いを防いだ • かつ

    Isabelle の自動証明によって 手軽に証明を完了できた
  86. 86 先ほどは雑な推測により length を決めたが 別のより小さい length も試してみたい より小さい

  87. 87 ⟹ 証明 証明したいこと

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

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

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

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

    capacity が変わりうる このようなプログラムも検証してみよう
  92. 92 先頭は内部の slice の長さ slice が平たく埋め込まれている 先頭の数値をみながら 分割した slice を返す

    入力の長さが一定なら 埋め込まれた slice が 長いほど荒く分割されて 外側の slice は短くなる
  93. 93

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

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

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

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

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

  99. 99 まずループ条件評価直前の j の値をループ回数 n と xs であらわそう ループ n

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

    n 回目 するとループ条件 v (n, xs) < len(xs) をはじめて 偽にする最小の n がループ終了までの回数になる j ループ条件 v (n, xs) < len(xs) をはじめて 偽にする最小の n j
  101. 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
  102. 102 実際に v (n, xs) の表現を考えてみる k の宣言時点では前回のループの j の値を参照している

    j 前回のループの j の値 ループ条件評価直前の j の値を v (n, xs) とする j ループ n 回目
  103. 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
  104. 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
  105. 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
  106. 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
  107. 107 v (n, xs) についてはわかってきたので 検証したいことを再確認しよう j

  108. 108 xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n, xs)

    < len(xs) がなりたつ 範囲でよい j
  109. 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
  110. 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 も場合ごとにわけて記述する
  111. 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 が与えられたときと考えればよい)
  112. 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 の場合
  113. 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 の場合
  114. 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
  115. 115 ⟹ 証明したいこと 証明 証明したいこと xss の length がループ回数より 大きいことを検証したい

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

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

    (n, xs) < len(xs) がなりたつ 範囲でよい j 証明 証明したいこと ただし検証するのは v (n, xs) < len(xs) がなりたつ 範囲でよい j
  118. 118 証明したいこと 証明 ⟹ 証明したいこと ⟹ この形をよくみると、 n ≦ v_j

    n xs がなりたてば、前提の ② v_j n xs < length xs から ③ がいえる: n < length xs ② ③ ①
  119. 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
  120. 120 証明したいこと ① 証明したいこと ② 証明の形をよくみると、 n ≦ v_j n

    xs 証明 ⟹ n ≦ v_j n xs 先にこれを証明して後の 証明を楽にしておこう このように本体の証明から 切り出した部分を 補題とよぶ
  121. 121 ⟶ 補題 証明

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

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

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

  125. 125 補題 ⟶ 証明 v_j は漸化式なので具体的な値がわかりづらい 無理やり n=k のときの具体的な v_j

    の値を求めても、 n=k - 1 のときの v_j の値が必要でこれを求めるには n=k - 2 のときの v_j の値が必要で、… のようにどこまでも展開できてしまう そこで 数学的帰納法 を使ってこの補題を解いてみよう
  126. 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) と読み替える
  127. 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) ここに先ほどの補題をあてはめることを考えよう: 補題 ⟶
  128. 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) がなりたつ
  129. 129 まず補題の n を 0 へ書き換えた: ⟶

  130. 130 まず補題の n を 0 へ書き換えた: そして n=0 のときの v_j

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

    の値は 0 とわかっている: ⟶ ⟶ どんな xs が与えられても 0 ≦ 0 はなりたつので P(0) はなりたつ
  132. 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) がなりたつ なりたつと証明できた
  133. 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) もなりたつ
  134. 134 8 自然数が 0 を含まない定義なら P(0) を P(1) と読み替える ⟶

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

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

    ⟹ ⟶ P(k) P(k+1) そこで k のときの v_j の値だけがあらわれるように 不等式を変形すればすぐ証明できる なお、時間が足らないのでここでの証明は割愛する
  137. 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) もなりたつ
  138. 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) を思い出すと: 補題 ⟶
  139. 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) と読み替える 数学的帰納法により補題を証明できた!
  140. 140 ⟶ 補題 証明

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

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

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

    panic が起こらないことを検証できた
  144. 144 1. 簡単な例の証明 2. 少し難しい例の証明 3. 定理証明支援系 4. 現実的な例の証明

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

    capacity を検証できた
  146. 146 もう少しだけ続きます

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

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

    結果がソートされている • …
  149. 149 実は従来の自動テストの本質も 性質 の検証 ただし自動テストは簡単だが 網羅性はない

  150. 150 もし従来のテストで非現実的と諦めた検証が あるなら 形式検証 を試してみてほしい(例) • どんな入力も考慮されている • デッドロックしない •

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

  152. 152

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

  154. 154

  155. 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
  156. 156 私の現場では、少し不安が残るがプログラム 意味論を使わない別の方法をとっている この方法を Isabelle ファースト と呼んでいる Isabelle ファースト とこれの対になる

    Go ファースト を比較してみよう
  157. 157 先に Go のプログラムを書き、ここから Isabelle のプログラムと定理に書き換える 先に Isabelle のプログラムと定理を書き、 ここから

    Go のコードへ書き換える
  158. 158 12 defer とか panic とかでどういう定理がなりたつんですか!?? のように なぜか Go の機能や標準ライブラリの証明までする羽目になる

    証明が難しい!Go のいろいろな事情12 に 振り回されるので大変 すでに進んでしまったプロジェクトに 後からいれやすい
  159. 159 証明しやすい。証明に不要な Go の事情を全部 省いて証明しやすい状態からスタートできる。 Go のコードに落とすのは難しくない defer や panic、関数ポインタなどは証明が

    難しいので進んでは使えない
  160. 160

  161. 161

  162. 162

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

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

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

  166. 166 実は身近にいろいろ 活躍できる場面 がある: • 私の現場の 3-way merge ツールのような 失敗が一見わからない上に放置すると

    復旧が高コストになるパターン • 数学的にきれいに仕様を書けるが 入力パターン数が多いもの(ソートなど)
  167. 167

  168. 168 プログラムの性質を手軽に検証する方法を紹介 • 例として Go の capacity の十分性を検証 • 来るべき証明が必要な開発に備えて

    定理証明支援系 を学んでいきましょう
  169. 169