lambda calculus

36
ラムダ計算と関数型言語を学ぶ naokirin 平成 25 7 28

Upload: naoki-rin

Post on 27-May-2015

1.412 views

Category:

Documents


0 download

DESCRIPTION

ラムダ計算と関数型言語について

TRANSCRIPT

Page 1: Lambda calculus

ラムダ計算と関数型言語を学ぶ

naokirin

平成 25 年 7 月 28 日

Page 2: Lambda calculus

目 次

第 1章 はじめに 3

第 I部 ラムダ計算の基礎 4

第 2章 λ式の定義 5

2.1 λ抽象 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2.2 関数適用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2.3 λ式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

第 3章 λ式の等式関係 9

3.1 高階関数とカリー化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3.2 束縛変数と自由変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3.3 α変換 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3.4 β 変換と λ式間の等式関係 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3.5 外延性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

第 4章 不動点コンビネータ 15

4.1 不動点と不動点コンビネータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4.2 様々な不動点コンビネータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4.2.1 Yコンビネータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4.2.2 Zコンビネータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4.2.3 Θコンビネータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

第 5章 リダクション 18

5.1 チャーチ・ロッサー定理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

5.2 リダクション戦略と正規化定理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

第 6章 データとその演算の表現 20

6.1 真偽値 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

6.2 組 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

6.3 自然数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

第 7章 λ定義可能と計算可能 23

7.1 λ定義可能な関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

7.2 帰納的関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

7.2.1 原始帰納的関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

7.2.2 帰納的関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

7.3 λ定義可能性と帰納的関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

第 8章 コンビネータ理論 31

1

Page 3: Lambda calculus

第 9章 型付きラムダ計算 32

第 10章 型付きラムダ計算とCoq 33

付 録A 各種定理の証明 34

A.1 チャーチ・ロッサー定理の証明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

A.2 正規化定理の証明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

A.3 Ackermann関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

参考文献 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

2

Page 4: Lambda calculus

第1章 はじめに

ラムダ計算はプログラムの意味論の際に, プログラミング言語の本質的な面を残して抽象化した理論体系として用いられているものです. 特にラムダ計算は関数型言語の基礎理論としても有名です.

またチューリングマシンと等価なモデルとしても知られています. ここではそのラムダ計算について簡単な概要と関数型言語の意味論を解説していく事にします. また型の概念を導入した型付きラムダ計算についても触れようと思います.

また本稿では理論と実際のプログラミング言語との比較をしていくことを 1つの目的としています. そのため実際の関数型言語の例として, OCamlと Haskellを取り上げる事にしています. また証明や説明のために定理証明支援器である Coqを使用します. ただし本稿では詳細な各プログラミング言語の文法やツールの使用方法などの解説は行わずに利用するため, 少しは OCamlや Haskell等の実際の関数型言語を知っていることを前提としています. 例を取り上げる際はそれほどむずかしいものではなく, ある程度のプログラムに対しての解説を入れるつもりではありますが, 万が一理解できないような場合は各自でそれら言語について入門書やWeb等で確認してください. 本稿ではプログラムを記述する際に分かりにくい場合はコメントでどの言語で書かれたものかを明示することにしますが, 明らかな場合は記述しないことにします. また対話環境への入力を表す場合は行頭に ?を付けることにします.

最後に読む上での注意点ですが, 本稿は私が自らの学習を目的として作成したものです. 誤謬や誤字, 脱字等も多く存在する可能性があります. その点に留意した上で読む場合には自己責任でお願いします. 内容に誤りがある場合は教えていただけると幸いです.

3

Page 5: Lambda calculus

第I部

ラムダ計算の基礎

Page 6: Lambda calculus

第2章 λ式の定義

2.1 λ抽象プログラミングの要素としては様々なものが存在します. 値や変数, 文や式, 関数などです. ここで

は関数について考えてみましょう. 例えば「1を足す」関数を考えます. (ただし, +は足し算としてすでに定義されているものとします. )

succ(x) = x+ 1

ここでこの関数には”successor”の意味で”succ”という名前をつけました. しかし本質としては”succ”

と言う名前は必要ではありません. そこで仮引数と式のみを取り出して,

λx.x+ 1

のように記述する事にします. このような操作を λ抽象といいます.

2.2 関数適用関数を用いる際は呼び出しを行います. 例えば前節で定義した succ(x)を呼び出す場合は例えば次

のようにします.

succ(0)

この操作は関数適用と呼ばれます. 関数適用は λ抽象による関数では次のようになります.

(λx.x+ 1)(0)

このように呼び出された関数は評価されます. 上記のような場合には

(λx.x+ 1)(0) = 0 + 1 = 1

のように評価されます. 最初の仮引数 xを実引数 0に置き換える操作は β 変換と言います. ラムダ計算において特に重要なのは λ抽象と関数適用, およびこの β変換です. この β変換は一般にはどのような順序で行っても問題はありません. しかし

(λy.(λz.z))((λx.xx)(λx.xx)) → (λy.(λz.z))((λx.xx)(λx.xx)) → · · ·

のようにうまく変換していかないと簡潔な式に変換することが出来ない場合もあります (上記の場合なら (λz.z)に出来るはずですが, 後ろの (λx.xx)(λx.xx)の関数適用に着目している限り出来ません).

実際のプログラムにおいてはこのような事態は深刻な問題です. この問題をどのように解決するかについては後のリダクションについて話す際に詳しく説明する事にします.

また実際のプログラムにおける関数適用においては評価戦略と言うものも存在します. 評価戦略は一般に値呼び出し (call-by-value), 名前呼び出し (call-by-name), 必要呼び出し (call-by-need)が知られています.

5

Page 7: Lambda calculus

値呼び出しは実引数が評価されてからその値を仮引数に渡します. 例えば次のような関数への適用を考えると

(λx.x+ x)((λy.y + 1)(1))

→ (λx.x+ x)(2)

→ 4

となります.

名前呼び出しは外側の関数の仮引数から関数適用を行います. そのため実引数の式が評価されること無くそのまま仮引数に渡されます.

(λx.x+ x)((λy.y + 1)(1))

→ ((λy.y + 1)(1) + ((λy.y + 1)(1)))

→ (2 + 2)

→ 4

となります. 名前呼び出しは例のように同じ式をコピーすることで無駄な計算が増えてしまうという欠点があります.

必要呼び出しは名前呼び出しと同様に外側の関数の仮引数から関数適用を行いますが, 一度評価した式は最初に評価したときの値を用いるという戦略を取ることで名前呼び出しの計算が増えてしまうという欠点を解消しています. 評価戦略についてのより詳細な説明については後で再度行う事にして, ここでは実際のプログラミング言語で関数適用時の評価戦略の違いを簡単に見て終えることにします.

まずは値呼び出しにおける例として OCamlを見てみましょう. 例として引数に無限に 1を足すadd inf という関数と, 何か 1つを引数に受け取り, 1を返す関数 oneを定義します. ちなみにOCaml

では関数や変数を定義する際には let, 再帰関数の定義の際には let recを使います.

(* OCaml code *)

let rec add inf x = add inf (x+ 1)

let one x = 1

次に関数 oneに (add inf 1)を適用して, 呼び出してみます.

? one (add inf 1); ;

これは停止しないプログラムになります. なぜなら値呼び出しでは実引数が評価されてからその値を仮引数に渡すので, (add inf 1)をまず最初に評価しようとします. しかし (add inf 1)は無限に足し上げる計算を行い続ける事になるため, 結果として停止しません.

6

Page 8: Lambda calculus

では必要呼び出しの例として Haskellで同じように呼び出しを行ってみましょう. 先ほどと同じように 2つの関数を定義します.

{- Haskell code -}add inf x = add inf (x+ 1)

one x = 1

次に対話システム (ghci)等で one (add inf 1)を呼び出してみましょう.

? one (add inf 1)

1

今度はちゃんとプログラムが終了するはずです. これは必要呼び出しにおいては必要になるまでの間,

式の評価が行われないために (add inf 1)を評価する事無く oneを評価するために起こります. この評価方法は一般に遅延評価 (lazy evaluation)とも呼ばれます1.

ここでは値呼び出しが必要呼び出しよりも優位である点のみを扱っていますが, 値呼び出し, 必要呼び出しそれぞれの長所, 短所あります. その点については関数型言語の意味論を説明する際に説明することにします.

2.3 λ式ラムダ計算において計算可能な式は λ式 (λ-term)と呼ばれ, 次のように BNFで再帰的に定義さ

れます.

定義 2.3.1 λ式の定義

⟨variable⟩ ::= v | ⟨variable⟩′

⟨λ−term⟩ ::= ⟨variable⟩ | (⟨λ−term⟩ ⟨λ−term⟩) | (λ ⟨variable⟩ . ⟨λ−term⟩)

上記の BNFで定義したものを日本語にすると

(1) 変数 v, v′, v′′, · · ·は λ式である.

(2) E, E′ が λ式のとき, (EE′)は λ式である.

(3) xが変数, E が λ式のとき, (λx.E)は λ式である.

となります. 2.は関数適用, 3.は λ抽象に当たります. λ式としては, たとえば

((λv.v)v′), (v(λv′.v′v′′))

などがあります. 2つ目の例のように (関数プログラミングの概念としては一般的ですが), 変数に関数適用できる事, 関数適用する実引数が λ式で良いということにも注意が必要です. ちなみに今後は断らない限り, 小文字を変数, 大文字を任意の λ式として扱います. また構文的に同値な λ式をE1 ≡ E2 と書く事にします.

1「遅延評価」という単語は, 曖昧さが存在するので, 本稿で「遅延評価」と言ったときは必要呼び出しについてのみを指すことにします.

7

Page 9: Lambda calculus

ここで定義 2.3.1のみでは, λ式の括弧が非常に多くなるので次のような略記を使っていく事にします.

(1) E1E2 · · ·En ≡ ((· · · ((E1E2)E3) · · ·)En)

(2) λx1x2 · · ·xn.E ≡ (λx1.(λx2.(· · · (λxn.E) · · ·)))

この略記を使うと((λx.(λy.x))z) ≡ λxy.x(z)

のように書く事が出来ます. 以上で λ式を定義しました.

ここで用語として部分式というものを説明してこの節を終えることにします. 部分式とは λ式を上記の定義で定義する際に λ式内に含まれる λ式のことを言います. つまり λxy.xy(λz.z)のような場合には, x, y, zが 1つずつ, (λz.z), xy(λz.z), λxy.xy(λz.z)が部分式に当たります. しかし仮引数を明示している λxの x等は部分式ではありません.

最後に部分式を厳密に定義すると, 次のような再帰的な定義となります.

定義 2.3.2 部分式の定義λ式 E について,

(1) E ≡ x (変数)のとき, その部分式は xである

(2) E ≡ E1E2 のとき, E1E2 は部分式であり, E1, E2 の部分式も E の部分式である

(3) E ≡ λx.E′ のとき, λx.E′ は部分式であり, E′ の部分式も E の部分式である

となります.

8

Page 10: Lambda calculus

第3章 λ式の等式関係

λ式において様々な式を書くことが出来ますが, それらの間にはと等式関係が成り立つものが存在します. 本章ではそれらの λ式の間の等式関係, 特に α変換と β 変換について説明していきます.

またそれらに関連して高階関数とカリー化, 束縛変数と自由変数にも触れていく事にします. さらにこの節の最後で λ式間の等式関係を導く形式体系を定義します.

3.1 高階関数とカリー化一般に関数は次のように複数の引数を持つ事が出来ます.

f(x1, x2, · · · , xn)

これらは λ式においては次のように定義するのが妥当でしょう. (説明のためにあえて略記を使わずに記述しています)

λx1.(λx2.(· · · (λxn.f(x1, x2, · · · , xn)) · · ·))

この関数は実引数を1つ渡す (ここではaを渡したとする)ことによって, λ式, λx2.(· · · (λxn.f(a, x2, · · · , xn)) · · ·)を返します. このように複数の引数を持つ関数を引数が 1つの関数で表すように変える操作をカリー化と言います. また引数に関数を受け取る関数, 関数を返す関数のことを高階関数と言います.

それでは OCamlを例にカリー化を見てみましょう1. まずはカリー化されていない関数の例として 2つの引数を足し算する plus uncurryを定義します.

(* OCaml code *)

let plus uncurry (x, y) = x+ y

このように定義してしまった場合には x, y について同時に適用する必要があります. これをカリー化した関数 plus curryにしてみましょう.

let plus curry x y = plus uncurry (x, y)

このようにすることで, 2つの引数 x, yについて同時に適用する必要がなくなります. そのため例えば引数に 1を足して返すような関数 add oneを定義したい場合は plus curryを使うことで

let add one = plus curry 1

とすることで定義することが出来ます. λ式でいうと add oneは (λxy.x + y)(1) = λy.1 + y に対応します.

1余談ではありますが, 関数型言語の多くでは基本的にカリー化された関数しか定義できない場合がほとんどです. そのため, ここではタプル (tuple, または組. 一般に (x1, x2, · · · , xn) のようなデータのまとまりのことであり, 集合論的には直積に対応するものをいう) を引数にすることでカリー化されていない関数であるとしています. また多くの場合でこのような説明が行われています.

9

Page 11: Lambda calculus

実際に利用すると

? add one 2; ;

3

のようになります.

それでは先ほどのようなカリー化されていない関数をカリー化された関数にする関数 curry を定義してみましょう.

(* OCaml code *)

let curry f x y = f (x, y)

これは高階関数の例であり, カリー化された関数の例にもなっています. たとえば, この関数を使って先ほどの関数 plus uncurryから関数 plus curryを作ってみましょう.

let plus curry = curry plus uncurry

Haskellには同じようなカリー化をする関数 curryが定義されています.

3.2 束縛変数と自由変数λ式において, λ抽象に当たる式では次のような式も書くことが出来ます.

λx.xy

ここで xについては仮引数として現れていますが, yについては仮引数として現れていません. このように仮引数として現れている変数を束縛変数と呼び, 仮引数に現れていない変数を自由変数と言います. ここである λ式 E の自由変数の集合を FV(E)と記述すると次のように FV(E)は定義できます. ちなみに要素として x1, x2, · · · , xn を含む集合は, {x1, x2, · · · , xn}のように書き, ある集合 S からある集合 T の含む要素を除いた集合は S\T と表しています.

定義 3.2.1 λ式 E における自由変数全体の集合 FV(E)の定義

FV(x) = {x}

FV(E1E2) = FV(E1) ∪ FV(E2)

FV(λx.E) = FV(E)\{x}

自由変数を含まないような λ式の事をコンビネータといいます.

10

Page 12: Lambda calculus

3.3 α変換ここで λ式を等式の成り立つ別の λ式へ変換する方法を考えます. 例えば次のような 2つの λ式

を考えてみましょう.

λxy.x, λzy.z

この 2つの λ式は等式関係が成り立つでしょうか. この 2つの λ式の違いは束縛変数の名前の違いだけです. このような束縛変数の名前が違うだけの λ式は同じだと言えるでしょう. このような 2つの λ式をつなぐ, 束縛変数の名前の付け替えを行うような変換を α変換と言います.

ここで「名前の付け替え」という非常に曖昧な表現を用いたので, もう少し正確にこのことを定義しましょう. 今後のために, より範囲を広げて λ式の置き換えまで定義してしまうことにしましょう.

定義 3.3.1 λ式の置き換えの定義λ式 E の中の変数 xを λ式 P に置き換えた λ式 E[P/x]を次のように定義する.

(1) E ≡ y(変数)のとき,

(a) y ≡ xなら, E[P/x] ≡ P

(b) y ̸≡ xなら, E[P/x] ≡ y

(2) E ≡ E1E2 のとき, (E1E2)[P/x] ≡ (E1[P/x])(E2[P/x])

(3) E ≡ λy.E′ のとき

(a) x ≡ yなら, E[P/x] ≡ λy.E′

(b) x ̸≡ yで, y ̸∈ FV(P )か x ̸∈ FV(E′)なら, E[P/x] ≡ λy.E′[P/x]

(c) x ̸≡ yかつ y ∈ FV(P )かつ x ∈ FV(E′)なら, E[P/x] ≡ λy′.E′[y′/y][P/x].

ただし y′ ̸∈ FV(E′) ∪ FV(P )

定義 3.3.1を用いると, α変換はλx.E = λy.E[y/x]

のような変換であると言えます. しかしこのような α変換を行っただけの 2つの λ式は本稿では構文的に同値であるとみなすことにします.

11

Page 13: Lambda calculus

3.4 β変換とλ式間の等式関係β 変換については 2.1節ですでに紹介したように, 仮引数を実引数に置き換える操作のことを言い

ます. この操作を行う前後で λ式は等式です. このような等式な λ式の等式の関係を導くための公理と推論規則をここでは導入することにしましょう.

(λx.E1)E2 = E1[E2/x] (E-APPABS)

E1 = E2

λx.E1 = λx.E2

(E-ABS)

E1 = E2

E1P = E2P(E-APP1)

E1 = E2

PE1 = PE2

(E-APP2)

E = E (E-EQ1)

E1 = E2 E2 = E3

E1 = E3

(E-EQ2)

E1 = E2

E2 = E1

(E-EQ3)

上記の公理と推論規則から導くことの出来る等式関係 E1 = E2 について, λ ⊢ E1 = E2, あるいは単に E1 = E2 と書いて, E1 と E2 は β変換可能であると言います.

ここでは計算をするということよりも等式関係に焦点を当てましたが, 計算においてはより簡単な形の λ式へと置き換えていく必要があります. そのことについてはリダクションの章で説明することにします.

ここまででラムダ計算における基本的な体系を定義することが出来ました. 次節ではここで定義したラムダ計算には含まれていない外延性を導入することで自然に拡張されたラムダ計算を見ることにします.

3.5 外延性これまでに導入してきた推論規則と公理をもう一度見直してみると, 当たり前のように見える次の

規則が存在しないことが分かります (ただし, x ̸∈ FV(E)です).

λx.Ex = E (E-EXT1)

これはある関数 f と gが全ての値について, f(x) = g(x)なら f = gであるという意味です. これは外延性と呼ばれる性質です. 理論的な意味合いは別にしても, 直感的には同値だと見なすのは非常に自然であると言えます.

さて上記に挙げた規則 (E-EXT1)を含む体系を λ+(E-EXT1)と表すことにします. 同等な体系として次の推論規則を含む λ+(E-EXT2)も考えられます (ただし, x ̸∈ FV(E1) ∪ FV(E2)です).

12

Page 14: Lambda calculus

E1x = E2x

E1 = E2

(E-EXT2)

実際にこの 2つの体系が同等であるかは証明する必要があるでしょう. ここでは Coqという定理証明支援器を用いて証明を行ってみることにしましょう. もちろん紙上での証明も可能です. ここでは (E-EXT2) ⇒ (E-EXT1)と (E-EXT1) ⇒ (E-EXT2)の 2つに分けて証明を行いましょう. それぞれ λ+(E-EXT2)が (E-EXT1)を満たすこと, また λ+(E-EXT1)が (E-EXT2)を満たすことを証明します. Coqでの定義では型付けが行われていますが, 本質的に同じものです.

(E-EXT2) ⇒ (E-EXT1)の証明� �(* 推論規則 (E-EXT2)を公理として導入 *)

Axiom ext2 : forall X Y: Type f g : X -> Y,

(forall (x: X), f x = g x) -> f = g.

(* (E-EXT1)を満たすことを証明 *)

Theorem ext1 : forall X Y:Type f : X -> Y,

forall (x:X), (fun x => f x) = f.

Proof.

intros.

apply ext2.

reflexivity.

Qed.� �

13

Page 15: Lambda calculus

(E-EXT1) ⇒ (E-EXT2)の証明� �(* 推論規則 (E-EXT1)を公理として導入 *)

Axiom ext1 : forall X Y:Type,

forall (f : X -> Y) (x:X), (fun x => f x) = f.

(* 推論規則 (E-ABS)をλ式を関数適用の形に限定した規則を導入 *)

Axiom abs : forall X Y : Type,

forall (f g : X -> Y) (x:X),

(f x = g x) -> (fun x => f x) = (fun x => g x).

(* (E-EXT2)を満たすことを証明 *)

Theorem ext2 : forall X Y: Type f g : X -> Y,

forall (x: X), (f x = g x) -> f = g.

Proof.

intros.

assert (f_eta := ext1 f x).

assert (g_eta := ext1 g x).

rewrite <- f_ext1.

rewrite <- g_ext1.

assert (fg_abs := abs f g x).

assert (abs_eq := fg_abs H).

rewrite <- abs_eq.

reflexivity.

Qed.� �この 2 つの体系が同等であることがわかったので次からはこの拡張されたラムダ計算の体系は

λ+(E-EXT)と統一して書くことにします. そしてこの λ+(E-EXT)は外延的ラムダ計算と呼ばれます. (E-EXT2)は前章までのラムダ計算の体系 λの推論規則 (E-ABS)の自然な拡張をした推論規則であることを紹介してこの章を終わりにします. この推論規則においてE1 = (λx.P )x, E2 = (λx.Q)x

を考えると,

(λx.P )x = (λx.Q)

λx.P = λx.Q

となります. これは λ ⊢ (λx.E)x = E となるからです. これは (E-EXT2)の λ式 E1, E2 を λ抽象の形式 λx. · · ·のみに限定したものであることが分かります.

14

Page 16: Lambda calculus

第4章 不動点コンビネータ

第 3章で自由変数を含まない λ式をコンビネータと呼ぶと説明しました. 本章では重要なコンビネータの 1つとして, 不動点コンビネータを紹介することにします. 不動点コンビネータは再帰関数を定義するために用いることが出来ます.

4.1 不動点と不動点コンビネータ少しラムダ計算から離れて一般的な関数についてを考えることにします. 最初に次のような仮引数

に関数を受け取り, 関数を返す高階関数 F を考えてみましょう. その関数が次のような関係を満たしていたとします.

F (f) = f

このとき上記のような関係を満たすような f を F の不動点と言います. ラムダ計算においては任意の λ式について不動点が存在します.

ここで 1から n (> 0)までの自然数の合計の和を取る関数を考えましょう. 次のような再帰関数でそのような和を計算することが出来るでしょう.1

sum(x) = if x = 0 then 0 else x+ sum(x− 1)

つぎのような高階関数 F ′ も仮引数に sumを適用すると同じように和を取ることが出来ることが分かります.

F ′(f)(x) = if x = 0 then 0 else x+ f(x− 1)

つまり sumは F ′ の不動点であると言えます.

つぎに不動点コンビネータを紹介します. 不動点コンビネータ gは任意の関数 f に対して

f(g(f)) = g(f)

を満たすものを言います.

ここで不動点コンビネータが実際にどのように使えるかということを簡単に見るためにHaskellのプログラムを例にして, 見ていくことにします.

{- 不動点コンビネータ -}fpc f = f (fpc f)

sum n f 0 = 0

sum n f x = x + f (x− 1)

1ここで if then else のような構文がラムダ計算の枠組みでどのように表されるか気になった方は第 6章までしばしお待ちを.

15

Page 17: Lambda calculus

ここでは先ほど例で出した関数 sumを sum nとして定義しました. これを実際に和を計算させるには, 次のように不動点コンビネータ fpcを用いて呼び出せば良いことが分かります.

? fpc sum n 3

6

? fpc sum n 10

55

OCamlの場合には値呼び出しにより引数から先に評価されてしまい, つぎのような

let rec fpc f = f (fpc f)

の定義では, 実行時に fpcの呼び出しが無限に行われてしまいます. そのため, 次のように定義したりすることで呼び出しが無限に行われないようにすることが出来ます.

let rec fpc f x = f (fpc f) x

もしくは次のように遅延計算をさせる lazyを導入することで次のように定義することでも出来ます.

let rec fpc f = f (lazy (fpc f))

let rec sum n f x =

if x = 0 then 0

else x + Lazy.force(f)(x− 1)

ここで次の定理を示しておきましょう.

定理 4.1.1

任意の λ式 P に対して Q = λx1 · · ·xn.P [q/Q]を満たす P が存在する.

証明Q ≡ Y(λpx1 · · ·xn.P )

とおくと, 不動点コンビネータの性質から

Q ≡ (λpx1 · · ·xn.P )Q

であり, Q = λx1 · · ·xn.P [q/Q]が得られる. □

4.2 様々な不動点コンビネータ型付けの無いラムダ計算における不動点コンビネータは無数にあり, 全てを挙げることに意味はあ

りません. しかし有名なものをここではいくつか例示しておくことにします.

4.2.1 Yコンビネータ

ここでまたラムダ計算に戻って不動点コンビネータを考えていくことにします. 型なしラムダ計算において有名な不動点コンビネータとしてYコンビネータを見てみましょう.

定義 4.2.1 Yコンビネータの定義

Y ≡ λf.(λx.f(xx))(λx.f(xx))

16

Page 18: Lambda calculus

これが不動点コンビネータになっていることを証明するのは簡単です.

YE = (λx.E(xx))(λx.E(xx))

= E((λx.E(xx))(λx.E(xx)))

= E(YE)

ここで気がつくのはYコンビネータでは, xxのように自分自身に関数適用することになっています.

このような定義が可能なのは型付けがないことに起因しています2.

4.2.2 Zコンビネータ

Zコンビネータは値呼び出しのプログラミング言語でも利用できる不動点コンビネータです. これはYコンビネータの部分式を変換した次のようなものになります.

定義 4.2.2 Zコンビネータの定義

Z ≡ λf.(λx.f(λy.(xx)y))(λx.f(λy.(xx)y))

ここでは OCamlで Zコンビネータを定義してみましょう. しかし素朴に定義 4.2.2から定義しようとしてもおそらく型が解決が出来ずにエラーとなるでしょう. OCamlで Zコンビネータを定義する際には, ”equi-recursive type”と呼ばれる型を導入することで解決できます3. ”equi-recursive

type”の詳細は置いておくとして, OCamlで”equi-recursive type”を用いる場合は -rectypes というオプションを付けると用いることが出来ます. -rectypes オプションを付けた上で次のコードを定義してやれば, 定義できるでしょう.

let z = fun f → (fun x → (fun y → f (x x) y)) (fun x → (fun y → f (x x) y))

4.2.3 Θコンビネータ

チューリング不動点コンビネータと呼ばれる不動点コンビネータです.

定義 4.2.3 チューリング不動点コンビネータΘの定義

Θ ≡ (λxy.(y(xx)y))(λxy.y((xx)y))

2ここでは Y コンビネータをプログラム上で定義しませんでした. これは Y コンビネータを実際のプログラム上で定義するには工夫が必要となることが多いためです. 特に型付けのある言語ではY コンビネータの型付けは一般にはできません. しかし”equi-recursive type”と呼ばれるような型つけが利用できる言語では Y コンビネータの型付けができ, 定義できる場合があります. 多相性を持つ場合は 4.1 節での Haskell のコードのようにすることで不動点コンビネータを直接定義できます.

3OCamlの”equi-recursive type”は非常に面白く, 例えば let x = (1, x) のような定義も可能になります. これは”equi-recursive type”では type t = t → t のように型の定義が再帰をしていても両辺を等価だと見なすことができ, 定義ができるということに起因しています.

17

Page 19: Lambda calculus

第5章 リダクション

これまで等式関係のみを考えてきました. しかし計算においては, (E-APPABS)の左辺 (λx.E1)E2

から右辺 E1[E2/x]へと変換するようなもののみを考える必要があります. このような変換はリダクションと呼ばれます. この章では, 特に β 変換に関してのリダクションを扱います.

β 変換に関してのリダクションは次のように定義されます.

定義 5.0.4 β 変換に関するリダクション

(1) →β の定義

(a) (λx.E1)E2 →β E1[E2/x],

(b) E1 →β E2 ⇒ PE1 →β PE2,

(c) E1 →β E2 ⇒ E1P →β E2P ,

(d) E1 →β E2 ⇒ λx.E1 →β λx.E2.

(2) ↠β の定義

E ≡ E0 →β E1 →β · · · →β En ≡ P (n ≥ 0)のとき, E ↠β P と定義する.

(3) =β の定義

E1 ↠β E2, もしくは E2 ↠β E1 のとき, E1 =β E2 と定義する.

P ↠β Qのとき, 「Pは Qに β リダクションされる」といい, P →β Qは「Pは Qに 1ステップで β リダクションされる」と言います.

定義 5.0.5 β リデックスとコンストラクタム(λx.E1)E2の形の λ式を βリデックスという. また E1[E2/x]をそのコンストラクタムという. □

定義 5.0.6 β 正規形β リデックスを部分式に持たない λ式を β正規形という. □

λ式では一般に βリデックスを部分式として複数持つ場合があります. このような場合, リダクションを行う順序も複数存在することになります. さらにこのリダクションの順序によっては無限にリダクションが続き, β正規形にできない場合も存在します (2.2節で取り上げた例がこれに当たります).

また λ式自体が β 正規形にリダクションできない場合も存在します. 例えば

(λx.xx)(λx.xx)

はリダクションを行っても, λ正規形にすることは出来ません. このような項は発散すると言います.

また同じ λ式から異なる順序でリダクションを行って, β正規形が一意に定まるかどうかは自明ではありません. しかし次節で紹介するチャーチ・ロッサー定理が存在することが知られています.

18

Page 20: Lambda calculus

5.1 チャーチ・ロッサー定理定理 5.1.1 チャーチ・ロッサー定理P ↠β E1 かつ P ↠β E2 ならば, E1 ↠β Qかつ E2 ↠β Qとなる λ式 Qが存在する. □

この性質は合流性, またはチャーチ・ロッサー性と呼ばれます. この性質はある λ式から出発してリダクションを行い, 別々のリダクションに到達したとしても, さらにリダクションを行うことで同じ λ式に合流させることができるということを示しています.

このことから全ての λ式が高々1つの β正規形を持つことを示すことが出来ます. この証明は長いため, 付録 A.1で行うことにします. またチャーチ・ロッサー定理の証明についても付録 A.1で示すことにします.

チャーチ・ロッサー定理より, リダクションに対して複数のリデックスを選ぶことが可能であり, かつその複数のリダクションから同一の結果が得られることが分かります. しかしチャーチ・ロッサー定理では任意のリダクションの選択によって β 正規形を持つ β リデックスが β 正規形へリダクションできることを示しているわけではありません. 次の節では, このリダクションの戦略と関連する事項について紹介します.

5.2 リダクション戦略と正規化定理2.2節で βリダクションを行う順序によってはその λ式が β正規形を持っていても有限回の βリダ

クションによって β 正規形に出来ない場合が存在することに言及しました. この β 正規形にたどり着けるかを決める, リダクションを行う際のリデックスの選び方をリダクション戦略と言います. このリダクション戦略をうまく選択することによって, β正規形を持つ λ式は必ず β正規形へとたどり着くことが出来ます. これは次の最左戦略により達成することが出来ます.

定義 5.2.1 最左戦略βリダクションを行う際に, 最も左にある βリデックスをコンストラクタムに置き換える戦略を最

左戦略という. また最左戦略に基づいたリダクションを最左リダクションという. □

定理 5.2.1 正規化定理β 正規形を持つ λ式は, 最左戦略で β 正規形が求まる. □

正規化定理の証明に関しても, 付録 A.2で行うことにします.

最後に最左戦略以外のリダクション戦略として最右最内戦略と呼ばれる戦略を紹介します. これは最も右にある β リデックスの最も内側にあるものを選びコンストラクタムに置き換える戦略です.

最左戦略が名前呼び出しに対応する一方でこの最右最内戦略は値呼び出しに対応します. ある戦略でβ正規形が求まるときに最左戦略で β正規形が求まる一方, ある戦略で無限のリダクションが続くとき, 最右最内戦略でも無限のリダクションが続くことが知られています.

19

Page 21: Lambda calculus

第6章 データとその演算の表現

ここまでで (型なしの)ラムダ計算の基本的な事項について触れてきました. そこで関数や変数を概念とした λ式を用いることで関数プログラミングの概念を理論的に扱うことが出来ることを説明しました. しかしながら現実のプログラミングでは, 実際のデータが存在しなければなりません. 例えば整数や小数, 文字といった単純なデータから, リストなどのデータ構造なども存在します. これらをラムダ計算の枠組みの中ではどのように扱うのでしょうか. この章ではこれまで定義した λ式の定義を変更することなく, データ, そしてそのデータを扱う演算, 操作を表すことが出来ることを見ていきます. ただしラムダ計算においては特に数学的対象である数値や真偽値といったデータが重要であるため, それらに焦点を当てることにします.

6.1 真偽値まずは真偽値と条件式を λ式で表してみることにしましょう.

命題 6.1.1 真偽値の表現true, falseを次のように定義する.

true ≡ λxy.x, false ≡ λxy.y

このとき, λ式 P, Q, Rについて

if P then Q else R ≡ PQR

と定義すると,

if true then Q else R = Q

if false then Q else R = R

となる. □

この命題から, λ式によって真偽値と単純な条件式を表すことが出来ることが分かりました.

6.2 組組 (順序対)は 2つの成分を持つ対象を表すのに用いるものです1. よく知られている組には xy-座

標の点を表す (x, y)などがあります. この組を λ式を用いて定義します.

命題 6.2.1 組 (順序対)

λ式 P , Qについて⟨P,Q⟩ ≡ λx.xPQ

を定義する.

1ある順序対 ⟨a, b⟩ は a = b でない限り, ⟨a, b⟩ ̸= ⟨b, a⟩ です.

20

Page 22: Lambda calculus

このときfst ≡ λx.x true, snd ≡ λx.x false

を定義するとfst⟨P,Q⟩ = P, snd⟨P,Q⟩ = Q

となる. □

6.3 自然数次に λ式で自然数 (0を含むことにする)を表現することにしましょう. 自然数 nに対して, 次のよ

うな λ式を考えます.

定義 6.3.1 チャーチ数自然数 nに対応する λ式として

cn ≡ λfx.fn(x)

を定義する. ただし fn(x)はf(f · · · (f︸ ︷︷ ︸

n

x) · · ·)

を表す. □

この自然数の定義 cn は考案者の A. Churchから, チャーチ数と呼ばれます. さて, この cn は次のように定義された λ式を用いることで, 足し算, かけ算, べき乗の計算を行うことが出来ます.

命題 6.3.1 チャーチ数の足し算, かけ算, べき乗次の λ式を定義する.

Addc ≡ λwxyz.wy(xyz),

Multc ≡ λxyz.x(yz),

Expc ≡ λxy.yx

この λ式は自然数 p, qに対して次の性質を満たす.

Addccpcq = cp+q,

Multccpcq = cp∗q,

Expccpc1 = cpq (q > 0)

この命題の証明は読者にまかせることにします (かけ算, べき乗に関しては, 帰納法を用いることで証明できます). このようにして自然数を λ式を用いて定義することが出来ます. ただしこのような数学的対象を λ式で表現する方法は単一ではありません. 自然数に関してはチャーチ数以外に次のような定義が知られています.

定義 6.3.2 自然数の表現 (その 2)

自然数 nに対応する λ式として⌈0⌉ ≡ λx.x

⌈n+ 1⌉ ≡ ⟨false, ⌈n⌉⟩.

を定義する. □

21

Page 23: Lambda calculus

上記の定義を用いると, 足す 1, 引く 1, ゼロかどうかの判定が次の λ式で行うことが出来ます.

命題 6.3.2 足す 1, 引く 1, ゼロ判定次の λ式を定義する.

Succ ≡ λx.⟨false, x⟩

Pred ≡ λx.x false

IsZero ≡ λx.x true

このときSucc⌈n⌉ = ⌈n+ 1⌉

Pred⌈n+ 1⌉ = ⌈n⌉

IsZero⌈0⌉ = true, IsZero⌈n+ 1⌉ = false

さて, この命題を元に先ほどのチャーチ数と同じような足し算やかけ算を定義するにはどうすれば良いでしょうか. ここで第 4章での不動点コンビネータを思い出し, そこでの議論と同じようにして足し算やかけ算を定義してみましょう. まず自然数 nを足す関数を考えます.

addn(x) =

{n (x = 0)

1 + addn(x− 1) (x > 0)

このとき次の関数

fn(x) =

{n (x = 0)

1 + fn(x− 1) (x > 0)

を用いることで addn = fn(addn)となることが分かります. これは fn の不動点が addn であると言うことです. それでは fn を λ式で表してみましょう.

Fn ≡ λfx.if IsZero x then ⌈n⌉ then Succ(f(Pred x))

これを用いて addn に対応する λ式Addn は次のように書くことが出来ます.

Addn = ΘFn

さらにこの nを引数とすることでAdd = Θ(λn.Fn)

のように足し算をする λ式を定義できます. かけ算については読者の課題としておくことにします.

ここで紹介した自然数での例のようにあるデータを表すのに複数の定義が存在する場合には, これらを入れ替えたとしても振る舞いが変更されないことが望ましいでしょう. しかしながら, (型なしの)ラムダ計算においては, 表現されたデータに関数適用を行えばどのようにしてデータが λ式で実装されているかを知ることが出来てしまいます. この問題を解消する 1つの方法として, 「型」を導入することが挙げられます. この「型」の導入は第 9章で行うことにします.

22

Page 24: Lambda calculus

第7章 λ定義可能と計算可能

第 6章ではラムダ計算においてのデータの表現を見ましたが, ラムダ計算では一体どのような範囲の関数を定義できるのでしょうか. 実のところ, ラムダ計算で定義できる関数は計算可能な関数であることが知られています. さらにここで計算可能な関数とは一体どのような関数のことなのか, という疑問があります. この「計算可能な関数」を定義するためにチューリングマシンや帰納的関数, ラムダ計算といったいくつかの関数のクラスが考案され, それらは全て同値であることが示されています. ここでは計算可能な関数については帰納的関数に焦点を絞り, 説明します.

7.1 λ定義可能な関数ラムダ計算の中で定義できる関数を正確に定義することから始めてみましょう. それには第 6章で

見た自然数の表現 ⌈n⌉を用いて定義します.

定義 7.1.1 f を自然数の p引数を取る関数 f : Np → N として

F ⌈n1⌉ · · · ⌈np⌉ = ⌈f(n1, · · · , np)⌉

となる λ式 F が存在するとき, F は f を λ定義するという. また f は F で λ定義可能であるという.

上記では ⌈n⌉の表現を用いて定義を行いましたが, チャーチ数による定義も行うことができ, 2つは同値です.

7.2 帰納的関数帰納的関数の前に原始帰納的関数について定義しましょう.

7.2.1 原始帰納的関数

定義 7.2.1 原始帰納的関数の定義

(1) 定数関数f(x) = k (kは自然数)

は原始帰納的関数である.

(2) 後者関数f(x) = Succ x

は原始帰納的関数である.

(3) 射影関数fni (x1, x2, · · · , xn) = xi (1 ≤ i ≤ n)

は原始帰納的関数である.

23

Page 25: Lambda calculus

(4) 合成関数

g(x1, x2, · · · , xn), h1(x1, x2, · · · , xn), h2(x1, x2, · · · , xn), · · · , hn(x1, x2, · · · , xn)

が原始帰納的関数のとき,

f(x1, x2, · · · , xn) = g(h1(x1, x2, · · · , xn), h2(x1, x2, · · · , xn), · · · , hn(x1, x2, · · · , xn))

は原始帰納的関数である.

(5) 原始帰納

(a)

h1(x1, x2)

が原始帰納的関数のとき, 関数{f(0) = k, (kは自然数)

f(Succ x) = h(f(x), x)

は原始帰納的関数である.

(b)

g(x1, x2, · · · , xn), h(x1, x2, · · · , xn+2)

が原始帰納的関数のとき,{f(0, x1, x2, · · · , xn) = g(x1, x2, · · · , xn)

f(Succ x, x1, x2, · · · , xn) = h(f(x, x1, x2, · · · , xn), x, x1, x2, · · · , xn)

は原始帰納的関数である.

(1)は定数の自然数を返す関数を表しています. 次に (2)は自然数に 1を足す関数です. (3)は n個の引数を受け取り, その i番目の引数の値を返します. (4)は原始帰納的関数に原始帰納的関数を引数に渡した結果を返す関数は原始帰納的関数である, と言うことです.

(5)は次の定理を元にした定義となっています.

定理 7.2.1 帰納的に定義される関数n変数関数 g(x1, x2, · · · , xn)と n+ 2変数関数 h(y, x, x1, x2, · · · , xn)が与えられたとき,{

f(0, x1, x2, · · · , xn) = g(x1, x2, · · · , xn)

f(Succ x, x1, x2, · · · , xn) = h(f(x, x1, x2, · · · , xn), x, x1, x2, · · · , xn)

を満たす n+ 1変数関数 f(x, x1, x2, · · · , xn)がただ 1つ存在する.

この関数 f を gと hから帰納的に定義される関数という. □

g, hに対して引数に具体的な自然数を与えたときに g, hに対して具体的な関数値が求まるとき, 一般に f も具体的な関数値が求まることが言えます.

さて, このように定義した原始帰納的関数はどのような関数を含んでいるのでしょうか. 簡単な例を挙げていくことにしましょう.

24

Page 26: Lambda calculus

(1) 加法関数

(5)-(b)の定義において {g(x) = x

h(x1, x2, x3) = Succ x

とすることで関数 f は足し算を行う関数として定義できることが分かります. 例えば 2+3に関しては

f(2, 3) = h(f(1, 3), 1, 3) = Succ (f(1, 3))

= Succ (h(f(0, 3), 0, 3))

= Succ (Succ 3)

= Succ 4

= 5

のようになります.

ここでいくつかの足し算における性質を満たしているかを証明しておくことにしましょう.�原始帰納的関数として定義した加法関数の性質の証明 �Definition g (x : nat) := x.

Definition h (x y z : nat) := S x.

Fixpoint f (x y : nat) :=

match x with

| 0 => g y

| S z => h (f z y) z y

end.

(* f, g, hで unfoldするためのタクティク *)

Tactic Notation "unfold_fgh" :=

unfold f; unfold g; unfold h.

Tactic Notation "unfold_fgh_in" constr(E) :=

unfold f in E; unfold g in E; unfold h in E.

(* (Succ x) + y = x + (Succ y) *)

Theorem replacement_succ: forall (x y : nat), f (S x) y = f x (S y).

Proof.

intros.

induction x.

unfold_fgh.

reflexivity.

unfold_fgh. unfold_fgh_in IHx.

f_equal. apply IHx.

Qed.

25

Page 27: Lambda calculus

(* Succ(x + y) = (Succ x) + y *)

Theorem succ_f_is_f_succ: forall (x y : nat), S (f x y) = f (S x) y.

Proof.

intros.

unfold_fgh.

f_equal.

Qed.

(* Succ(x + y) = x + (Succ y) *)

Theorem succ_f_is_f_succ_rep: forall (x y : nat), S (f x y) = f x (S y).

Proof.

intros.

assert (rep_succ := replacement_succ x y).

rewrite <- rep_succ.

assert (succ_f := succ_f_is_f_succ x y).

apply succ_f.

Qed.

(* x + y = y + x *)

Theorem comutation_law: forall (x y : nat), f x y = f y x.

Proof.

intros.

induction x. induction y.

reflexivity.

unfold_fgh. unfold_fgh_in IHy.

rewrite <- IHy.

reflexivity.

assert (succ_f := succ_f_is_f_succ x y).

assert (succ_f_rep := succ_f_is_f_succ_rep y x).

rewrite <- succ_f. rewrite <- succ_f_rep.

f_equal.

apply IHx.

Qed.� �この他にも x+ y = x+ zならば y = z, (x+ y) + z = x+ (y + z)なども証明してみると良いでしょう.

26

Page 28: Lambda calculus

(2) 乗法関数

かけ算は先ほど原始帰納的関数として定義できた加法関数を addPR(x1, x2)としてこれを用いて定義しましょう. (5)-(b)の定義において{

g(x) = 0

h(x1, x2, x3) = addPR(x1, x2)

とすることで乗法関数を定義することが出来ます.

(3) 指数関数

自然数上の指数関数 axは次のように定義できることから, 原始帰納的関数であることが分かります. {

a0 = 1

aSucc x = ax · x

これは (5)の k = 1, h(x1, x2) = x1 · aとした関数 f に一致していると分かります.

このようにして自然数上での基本的な演算は原始帰納的関数として定義できます (減算, 除算に関しては自然数上での定義に工夫が必要になるため割愛しました). しかしながら, 具体的な関数値が求められても原始帰納的関数として定義できない関数が存在することが知られています.

7.2.2 帰納的関数

本題である帰納的関数に入る前に, 原始帰納的関数では不十分であることの具体例を見ることにします. 実際に関数の値が計算可能であるにも関わらず, 原始帰納的関数でない関数の例としては次のAckermann関数が有名です.

定義 7.2.2 Ackermann関数

Ack(m,n) =

n+ 1, if m = 0

Ack(m− 1, 1), if n = 0

Ack(m− 1, Ack(m,n− 1), otherwise

この関数は実際に計算可能です. しかしながらこの関数が原始帰納的関数でないことが証明できます (この証明に関しては付録A.3を参照). このような関数を含むような関数の集合を原始帰納的関数を拡張することで定義することにしましょう. 拡張のために µ演算子というものを導入することにします.

定義 7.2.3 µ演算子自然数上の述語 P (x1, x2, · · · , xn, y)に対して, µy(P (x1, x2, · · · , xn, y))は述語 P が真になる最小

の yを表す. ただし述語 P が真になる yが存在しないときは値は定義されない. □

この µ演算子を用いて原始帰納的関数の拡張として帰納的関数を次のように定義します.

定義 7.2.4 帰納的関数定義 7.2.1に次の定義を加えた関数の集合に属する関数を帰納的関数と呼ぶ.

27

Page 29: Lambda calculus

g(x1, x2, · · · , xn, y)が帰納的関数で

∀x1 · · · ∀xn∃(g(x1, x2, · · · , xn, y) = 0)

が成り立つとき,

f(x1, · · · , xn) = µy(f(x1, · · · , xn, y) = 0)

は帰納的関数である. □

上記で帰納的関数を定義しました. このように定義した帰納的関数が実際に具体的に計算できる関数であると言うことは断言できません. これは「具体的に計算できる」ということが数学的に定義できないからです. しかしながらこの「具体的に計算できる」に対する次のような仮説があります.

仮説 7.2.1 Churchの仮説具体的に計算できる全域関数は, 帰納的関数である. □

全域関数というのは部分関数という概念と対比して用いられる用語なので, 部分関数とともに説明します.

定義 7.2.5 全域関数と部分関数集合 Aの全ての要素を集合Bの要素に対応づけたものを関数と呼ぶ. また集合 Aの要素を集合B

の要素に高々1つ対応づけたものを部分関数と呼ぶ. またこの部分関数に対して前者を全域関数と呼ぶことがある. □

この部分関数を帰納的関数に適用し, 次の部分帰納的関数を定義しましょう.

定義 7.2.6 部分帰納的関数定義 7.2.1に次の定義を加えた関数の集合に属する関数を部分帰納的関数と呼ぶ. g(x1, x2, · · · , xn, y)

が部分帰納的関数のときf(x1, · · · , xn) = µy(f(x1, · · · , xn, y) = 0)

は部分帰納的関数である. □

この部分帰納的関数を用いて先ほどの Churchの仮説は次のように拡張されることがあります.

仮説 7.2.2 (拡張された)Churchの仮説具体的に計算できる関数は, 部分帰納的関数である. □

この仮説は多くの数学者に受け入れられています. そのためこの仮説を受け入れ, その上でラムダ計算の計算可能性を見ることにします.

7.3 λ定義可能性と帰納的関数前節では帰納的関数が計算可能な関数であるという Churchの仮説に行き着きました. それではラ

ムダ計算で定義される関数は帰納的関数でしょうか. またこの逆は成り立つでしょうか. このことを確かめてみましょう. そこで帰納的関数が λ定義可能かということをまず見ていくことにしましょう.

補助定理 7.3.1

定義 7.2.1の (1), (2), (3)の関数 f は λ定義可能である. □

28

Page 30: Lambda calculus

証明それぞれ次の λ式で λ定義可能である (ただし kは自然数).

λx.⌈k⌉, λx.⟨false, x⟩, λx1 · · ·xn.xi

補助定理 7.3.2

定義 7.2.1の (4)の関数 f は λ定義可能である. □

証明

g(x1, x2, · · · , xn), h1(x1, x2, · · · , xn), h2(x1, x2, · · · , xn), · · · , hn(x1, x2, · · · , xn)

が λ式 G,H1,H2, · · · ,Hn で λ定義可能ならば, (4)は

λx1x2 · · ·xn.G(H1x1x2 · · ·xn) · · · (Hnx1x2 · · ·xn)

で λ定義可能である. □

補助定理 7.3.3

定義 7.2.1の (5)の関数 f は λ定義可能である. □

証明(b)を証明すれば (a)は証明できていると言えるので (b)についてのみ, 証明を行う.

g(x1, x2, · · · , xn), h(x1, x2, · · · , xn+2)

が λ式 G,H で λ定義可能である場合

Fyx1x2 · · ·xn = if (IsZero y) then Gx1x2 · · ·xn

else H(F (Pred y)x1x2 · · ·xn)(Pred y)x1x2 · · ·xn

が存在すると言える. これは定理 4.1.1による.

この F について, yについての帰納法により

F ⌈y⌉⌈x1⌉ · · · ⌈xn⌉ = ⌈f(y, x1, · · · , xn)⌉

を証明することが出来る. y = 0について

F ⌈0⌉⌈x1⌉ · · · ⌈xn⌉ = if (IsZero ⌈0⌉) then G⌈x1⌉ · · · ⌈xn⌉

else · · ·

= G⌈x1⌉ · · · ⌈xn⌉

= ⌈g(x1, · · · , xn)⌉

= ⌈f(0, x1, · · · , xn)⌉

また y = kが成り立つとき, y = k + 1について

F ⌈k + 1⌉⌈x1⌉ · · · ⌈xn⌉ = if (IsZero ⌈k + 1⌉) then G⌈x1⌉ · · · ⌈xn⌉

else H(F (Pred ⌈k + 1⌉)⌈x1⌉ · · · ⌈xn⌉)(Pred ⌈k + 1⌉)⌈x1⌉ · · · ⌈xn⌉

= H(F ⌈k⌉⌈x1⌉ · · · ⌈xn⌉)⌈k⌉⌈x1⌉ · · · ⌈xn⌉

= ⌈h(f(k, x1, · · · , xn), k, x1, · · · , xn)⌉

= ⌈f(k + 1, x1, · · · , xn)⌉

29

Page 31: Lambda calculus

ここまでで原始帰納的関数が λ定義可能であることが言えました. 最後に µ演算子を用いて定義した帰納的関数の定義で現れた関数が λ定義可能かを見ることにしましょう.

仮説 7.3.1

帰納的関数 f は λ定義可能である. □

証明原始帰納的関数が λ定義可能であることはすでに証明したので, 帰納的関数が λ定義可能であるこ

とを示すには定義 7.2.4で定義した関数が λ定義可能であることを示せば良い.

λ式 Gにより gは λ定義可能であるとする. ここで hを次のように定義する.

h(y, x1, · · · , xn) = if g(y, x1, · · · , xn) = 0 then y

else h(y + 1, x1, · · · , xn)

するとf(x1, · · · , xn) = h(0, x1, · · · , xn)

である. 定理 4.1.1より

Hyx1 · · ·xn = if IsZero(Gyx1 · · ·xn)then y

else H(Succ y)x1 · · ·xn

を満たす λ式が存在する. 関数 f に関しては λx1 · · ·xn.H⌈0⌉x1 · · ·xn で λ定義可能である.

ここまでで帰納的関数が λ定義可能であることを示すことが出来ました. この逆も成り立つことが知られています.

定理 7.3.1

全ての λ定義可能な関数は帰納的関数である.

この証明については行わないことにします. それはこの証明を行うのが本稿の範囲を超えるためです.

さて, ここまでで次のことが言えました.

具体的に計算可能な関数⇔帰納的関数⇔ λ定義可能な関数

これによりラムダ計算の表現能力は十分であると言えます.

30

Page 32: Lambda calculus

第8章 コンビネータ理論

31

Page 33: Lambda calculus

第9章 型付きラムダ計算

32

Page 34: Lambda calculus

第10章 型付きラムダ計算とCoq

33

Page 35: Lambda calculus

付 録A 各種定理の証明

A.1 チャーチ・ロッサー定理の証明TODO

A.2 正規化定理の証明TODO

A.3 Ackermann関数TODO

34

Page 36: Lambda calculus

参考文献

[1] 横内寛文, プログラム意味論, 共立出版, 1994.

[2] 細井勉, 計算の基礎理論, 教育出版, 1975.

[3] Henk P. Barendregt, The Lambda Calculus, College Publications, 2012.

35