decreasing_by
decreasing_by
は、再帰関数などの停止性(計算が無限に続かないということ)を示し、Lean に定義を受け入れさせるために使われます。
使用例
たとえば、以下の関数はそのままでは Lean が停止性を示せないためエラーになってしまいます。
-- エラーになってしまう
def Nat.toListNat (n : Nat) : List Nat :=
if n == 0 then
[]
else
Nat.toListNat (n / 10) ++ [(n % 10)]
decreasing_by
に続けて停止することの証明を与えれば Lean に受け入れられるようになります。
def Nat.toListNat (n : Nat) : List Nat :=
if n == 0 then
[]
else
Nat.toListNat (n / 10) ++ [(n % 10)]
decreasing_by
-- `n ≠ 0 → n / 10 < n` を示す
grind
-- 動作テスト
#guard Nat.toListNat 1234 = [1, 2, 3, 4]
あるいは、decreasing_by
を使用しなくても、関数内で have
などを使って停止性を証明しておいても良いです。
def Nat.toListNat (n : Nat) : List Nat :=
if h : n == 0 then
[]
else
have : n / 10 < n := by
grind
Nat.toListNat (n / 10) ++ [(n % 10)]
部分関数を禁止するのはなぜか?
そもそもなぜ Lean では関数の停止性を示す必要があるのでしょうか?それは、Lean の論理体系の健全性を保つためです。1
例として、停止する保証がない関数を定義してみましょう。以下の関数は引数の数列の f
がいつか some
になる保証がないので、無限に計算が終わらない可能性があります。
variable {α : Type}
/-- 数列 `f : Nat → Option α` が `some` を返すような最初の `f n` を返す -/
unsafe def search (f : Nat → Option α) (start : Nat) : α :=
match f start with
| .some x => x
| .none => search f (start + 1)
ここで問題なのは、この関数 search
を使うと任意の型の項が構成できてしまうということです。
-- `α` は任意なので、どんな型の項でも作れることになる
unsafe def anything : α := search (fun _ => none) 0
したがって特に Empty
型の項を作ることもできて、カリー・ハワード同型対応により「型の項を作る」ことは「命題を証明する」ことに対応するので、矛盾が導かれたことになってしまいます。
unsafe example : False := by
have _ : Empty := anything
contradiction
1
ここで紹介している例は Joachim Breitner さんによる Recursive definitions in Lean というブログ記事から引用しています。