Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lambda calculus

2,151 views

Published on

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

  • Be the first to comment

Lambda calculus

  1. 1. ラムダ計算と関数型言語を学ぶ naokirin 平成 25 年 7 月 28 日
  2. 2. 目 次 第 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
  3. 3. 第 9 章 型付きラムダ計算 32 第 10 章 型付きラムダ計算と Coq 33 付 録 A 各種定理の証明 34 A.1 チャーチ・ロッサー定理の証明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 A.2 正規化定理の証明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 A.3 Ackermann 関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 参考文献 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 2
  4. 4. 第1章 はじめに ラムダ計算はプログラムの意味論の際に, プログラミング言語の本質的な面を残して抽象化した理 論体系として用いられているものです. 特にラムダ計算は関数型言語の基礎理論としても有名です. またチューリングマシンと等価なモデルとしても知られています. ここではそのラムダ計算について 簡単な概要と関数型言語の意味論を解説していく事にします. また型の概念を導入した型付きラムダ 計算についても触れようと思います. また本稿では理論と実際のプログラミング言語との比較をしていくことを 1 つの目的としていま す. そのため実際の関数型言語の例として, OCaml と Haskell を取り上げる事にしています. また証 明や説明のために定理証明支援器である Coq を使用します. ただし本稿では詳細な各プログラミン グ言語の文法やツールの使用方法などの解説は行わずに利用するため, 少しは OCaml や Haskell 等 の実際の関数型言語を知っていることを前提としています. 例を取り上げる際はそれほどむずかしい ものではなく, ある程度のプログラムに対しての解説を入れるつもりではありますが, 万が一理解で きないような場合は各自でそれら言語について入門書や Web 等で確認してください. 本稿ではプロ グラムを記述する際に分かりにくい場合はコメントでどの言語で書かれたものかを明示することに しますが, 明らかな場合は記述しないことにします. また対話環境への入力を表す場合は行頭に ? を 付けることにします. 最後に読む上での注意点ですが, 本稿は私が自らの学習を目的として作成したものです. 誤謬や誤 字, 脱字等も多く存在する可能性があります. その点に留意した上で読む場合には自己責任でお願い します. 内容に誤りがある場合は教えていただけると幸いです. 3
  5. 5. 第I部 ラムダ計算の基礎
  6. 6. 第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
  7. 7. 値呼び出しは実引数が評価されてからその値を仮引数に渡します. 例えば次のような関数への適用 を考えると (λ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
  8. 8. では必要呼び出しの例として 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
  9. 9. ここで定義 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
  10. 10. 第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
  11. 11. 実際に利用すると ? 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 の含む要素を除いた集合は ST と表しています. 定義 3.2.1 λ 式 E における自由変数全体の集合 FV(E) の定義 FV(x) = {x} FV(E1E2) = FV(E1) ∪ FV(E2) FV(λx.E) = FV(E){x} □ 自由変数を含まないような λ 式の事をコンビネータといいます. 10
  12. 12. 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
  13. 13. 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
  14. 14. 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
  15. 15. (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
  16. 16. 第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
  17. 17. ここでは先ほど例で出した関数 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
  18. 18. これが不動点コンビネータになっていることを証明するのは簡単です. 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
  19. 19. 第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
  20. 20. 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
  21. 21. 第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
  22. 22. このとき 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
  23. 23. 上記の定義を用いると, 足す 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
  24. 24. 第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) 射影関数 fn i (x1, x2, · · · , xn) = xi (1 ≤ i ≤ n) は原始帰納的関数である. 23
  25. 25. (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
  26. 26. (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
  27. 27. (* 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
  28. 28. (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
  29. 29. 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
  30. 30. 証明 それぞれ次の λ 式で λ 定義可能である (ただし 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
  31. 31. ここまでで原始帰納的関数が λ 定義可能であることが言えました. 最後に µ 演算子を用いて定義 した帰納的関数の定義で現れた関数が λ 定義可能かを見ることにしましょう. 仮説 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
  32. 32. 第8章 コンビネータ理論 31
  33. 33. 第9章 型付きラムダ計算 32
  34. 34. 第10章 型付きラムダ計算とCoq 33
  35. 35. 付 録A 各種定理の証明 A.1 チャーチ・ロッサー定理の証明 TODO A.2 正規化定理の証明 TODO A.3 Ackermann 関数 TODO 34
  36. 36. 参考文献 [1] 横内寛文, プログラム意味論, 共立出版, 1994. [2] 細井勉, 計算の基礎理論, 教育出版, 1975. [3] Henk P. Barendregt, The Lambda Calculus, College Publications, 2012. 35

×