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

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

DeNA_Tech
March 03, 2021

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

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

DeNA_Tech

March 03, 2021
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. 2

  2. 4

  3. 7

  4. 13

  5. 14

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

    何度も メモリの再確保 と コピー が走る これはそこそこ重い処理なので避けたい
  7. 18

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

    は 遅い • 巨大 capacity の事前確保は 無駄 → 必要最小限を一度に確保 がベスト!
  9. 23

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

    capacity 指定と append を使っていないが、 実際のコードではたやすく panic されては困る そのため、length 指定とインデックスアクセスを capacity 指定/append に読み替えてほしい
  11. 38 引数に与えられたslice と同じ 内容の新しいslice を作成して返す なお i, x := range

    xs の方が 読みやすいが、説明の しやすさのために崩している
  12. 42 現状をまとめると、i < len(xs) が なりたつと検証できれば ys[i] の 部分では panic

    しないといえる 検証したいことがわかったので 検証に使える材料を集めてこよう
  13. 45 これまでの事実をまとめると: ys[i] の直前で i < len(xs) が なりたっている 検証したいのは

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

    ys[i] の直前で i < len(xs) がなりたっていること この上から下を証明してみよう 前提より明らか i < len(xs) i < len(xs)
  15. 49 これまでの証明の過程で具体的な i や xs の 値を与えなかったことを思い出してほしい この証明は具体的な i や

    xs の値に依存して いないので、どんな i や xs でも当てはまる これが入力のサンプルを必要とする他の 検証方法にはない最大の利点
  16. 55 ループ n 回目 j の値 0 0 1 1

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

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

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

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

    をまだ 未設定だったので話を戻す j
  21. 61 ループ回数は len(xs) より小さい ので雑に length の指定に S(n) を 当てはめてみた(これは後にもう

    少し改善する) ただし雑に当てはめただけなので 問題がないかどうか検証していく
  22. 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
  23. 64 ys[j] の代入の直前で i は更新されて いないので、ys[j] の代入の直前で i < len(xs)

    がなりたつ(材料1) この時点では i < len(xs) が成立 ループ
  24. 66 前の方で v (n) は次のように あらわせることわかった: j v (n) =

    (材料3) n (n + 1) 2 j この時点の j の値が v (n) j ループ n 回目
  25. 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
  26. 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
  27. 71 定理証明支援系はその名のとおり数学の 証明を以下の 2 つの方向から支援する: 1. 証明を検査してくれる ◦ 検査された証明は正しいとわかる5 2.

    証明を一部手伝ってくれる ◦ 場合によっては全自動で証明できる 5 ここでは定理証明支援系の正しさを仮定している
  28. 92 先頭は内部の slice の長さ slice が平たく埋め込まれている 先頭の数値をみながら 分割した slice を返す

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

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

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

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

    j 前回のループの j の値 ループ条件評価直前の j の値を v (n, xs) とする j ループ n 回目
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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 も場合ごとにわけて記述する
  40. 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 が与えられたときと考えればよい)
  41. 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 の場合
  42. 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 の場合
  43. 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
  44. 116 証明したいこと xss の length がループ回数より 大きいことを検証したい ただし検証するのは v (n,

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

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

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

    xs 証明 ⟹ n ≦ v_j n xs 先にこれを証明して後の 証明を楽にしておこう このように本体の証明から 切り出した部分を 補題とよぶ
  49. 125 補題 ⟶ 証明 v_j は漸化式なので具体的な値がわかりづらい 無理やり n=k のときの具体的な v_j

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

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

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

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

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

  63. 154

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

    証明が難しい!Go のいろいろな事情12 に 振り回されるので大変 すでに進んでしまったプロジェクトに 後からいれやすい
  65. 160

  66. 161

  67. 162

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

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

  70. 169