章一覧

実例:ソートの正しさ

計算する関数と、その仕様を、別々に理解します。

18.1 何を証明したいのか

ソート関数に期待する性質は、「出力が昇順である」ことだけではありません。すべての要素が、消えず、増えず、並べ替えられただけであることも必要です。

ソートの仕様 = 出力は整列済み ∧ 出力は入力の並べ替えである

Tauto では、計算する関数 sort と、その性質を述べる定理を、分けて書けます。sort は第 6 章で書いた挿入ソートです(insert で、整列済みのリストに一つの要素を正しい位置へ入れます)。

18.2 完成する定理の形

この証明は、リポジトリの examples/sort/ にあります(proof/sort.taut)。整列済みであることを表す sorted は、各要素が次の要素以下であることを述べる命題を返す関数で、Perm は、順列を表す帰納的な命題です。

inductive Perm(A: Type): (List(A)) -> (List(A)) -> Prop {
  perm_nil(): Perm(A, Nil, Nil)
  perm_skip<x: A, l: List(A), m: List(A)>(h: Perm(A, l, m)): Perm(A, Cons(x, l), Cons(x, m))
  perm_swap<x: A, y: A, l: List(A)>(): Perm(A, Cons(x, Cons(y, l)), Cons(y, Cons(x, l)))
  perm_trans<l: List(A), m: List(A), n: List(A)>(a: Perm(A, l, m), b: Perm(A, m, n)): Perm(A, l, n)
}

二つの定理を別々に証明します。

theorem sort_perm<A>(l: List(A)): Perm(A, l, sort(l)) where Ord(A) { ... }
theorem sort_sorted<A>(l: List(A)): sorted(sort(l)) where Ord(A) { ... }

そして、AndIntro で合成すると、sort_correct が完成します。

theorem sort_correct<A>(l: List(A)): sorted(sort(l)) ∧ Perm(A, l, sort(l)) where Ord(A) {
  AndIntro(sort_sorted(l), sort_perm(l))
}

実際に検査します。

tauto check examples/sort/proof/sort.taut
ok      sort_perm
ok      insert_after
ok      insert_sorted
ok      sort_sorted
ok      sort_correct

(ほかの補題も並びます。)

重要なのは、sort_correct という名前の定理を、宣言しただけでは意味がないことです。補題が実際に検査され、仮定が何なのかを追跡できて初めて、根拠のある保証になります。この例は、公理を一つも使っていません。

18.3 使える範囲を考える

このソートは、Ord(A) を満たす型に対して成り立ちます。証明が使う A の性質は、Ord の法則だけです。比較の法則が成り立たない「順序」に対しては、証明の前提が崩れます。一般的な定理ほど、前提条件を正確に理解することが大切です。

演習 18.1:出力が昇順でも、要素を一つ消してしまう実装が「ソート」として不正なのは、どの仕様を破っているからでしょうか。