ラムダ計算

ラムダ計算(ラムダけいさん、英語: lambda calculus)は、計算模型のひとつで、計算の実行を関数への引数評価英語: evaluation)と適用英語: application)としてモデル化・抽象化した計算体系である。ラムダ算法とも言う。関数を表現する式に文字ラムダ (λ) を使うという慣習からその名がある。アロンゾ・チャーチスティーヴン・コール・クリーネによって1930年代に考案された。1936年にチャーチはラムダ計算を用いて一階述語論理決定可能性問題を(否定的に)解いた。ラムダ計算は「計算可能な関数」とはなにかを定義するために用いられることもある。計算の意味論型理論など、計算機科学のいろいろなところで使われており、特にLISPMLHaskellといった関数型プログラミング言語の理論的基盤として、その誕生に大きな役割を果たした。

ラムダ計算は1つの変換規則(変数置換)と1つの関数定義規則のみを持つ、最小の(ユニバーサルな)プログラミング言語であるということもできる。ここでいう「ユニバーサルな」とは、全ての計算可能な関数が表現でき正しく評価されるという意味である。これは、ラムダ計算がチューリングマシンと等価な数理モデルであることを意味している。チューリングマシンがハードウェア的なモデル化であるのに対し、ラムダ計算はよりソフトウェア的なアプローチをとっている。

この記事ではチャーチが提唱した元来のいわゆる「型無しラムダ計算」について述べている。その後これを元にして「型付きラムダ計算」という体系も提唱されている。

歴史

[編集]

元々チャーチは、数学の基礎となり得るような完全な形式体系を構築しようとしていた。彼の体系がラッセルのパラドックスの類型に影響を受けやすい(例えば論理記号として含意 → を含むなら、λx.(x→α) にYコンビネータを適用してカリーのパラドックスを再現できる)ということが判明した際に、彼はそこからラムダ計算を分離し、計算可能性理論の研究のために用い始めた。この研究からチャーチは一階述語論理の決定可能性問題を否定的に解くことに成功した。

非形式的な概説

[編集]

例えば、ある数に 2 を加える関数 f を考える。これは通常の書き方では f(x) = x + 2 と書くことができるだろう。この関数 f は、ラムダ計算の式(ラムダ式という)では λx. x + 2 と書かれる。変数 x の名前は重要ではなく、 λy. y + 2 と書いても同じである。同様に、この関数に 3 を適用した結果の数 f(3) は (λx. x + 2) 3 と書かれる。関数の適用は左結合である。つまり、 f x y = (f x) y である。今度は、引数(関数の入力)に関数をとりそれに 3 を適用する関数を考えてみよう。これはラムダ式では λf. f 3 となる。この関数に、先ほど作った 2 を加える関数を適用すると、 (λf. f 3) (λx. x + 2) となる。ここで、

f. f 3) (λx. x + 2)    と    (λx. x + 2) 3    と    3 + 2

の3つの表現はいずれも同値である。

ラムダ計算では、関数の引数は常に1つである。引数を2つとる関数は、1つの引数をとり、1つの引数をとる関数を返す関数として表現される(カリー化)。例えば、関数 f(x, y) = xy は λx. (λy. xy) となる。この式は慣例で λxy. xyと省略して書かれることが多い。以下の3つの式

xy. xy) 7 2    と    (λy. 7 − y) 2    と    7 − 2

は全て同値となる。

ラムダ計算そのものには上で用いた整数や加算などは存在しないが、算術演算や整数は特定のラムダ式の省略であると定義することによってエンコードできる。その具体的な定義については改めて後に述べる。

ラムダ式は自由変数( λ によって束縛されていない変数)を含むこともできる。例えば、入力に関係なく常に y を返す関数を表す式 λx. y において、変数 y は自由変数である。このようなときに変数名の付け替えが必要になることがある。つまり、式 (λxy. y x) (λx. y) は λy. y (λx. y) ではなく、 λz.z (λx. y) と同値である。

定義

[編集]

ここではラムダ計算の形式的な定義を述べる。まず、記号 (identifier) の可算無限集合 {a, b, c,…, x, y, z,…} を導入する。全てのラムダ式の集合は、BNFで書かれた以下の文脈自由文法によって定義される。

  1. <expr> ::= <identifier>
  2. <expr> ::= (λ<identifier>. <expr>)
  3. <expr> ::= (<expr> <expr>)

最初の2つの規則は関数の定義を表しており、3つめの規則は関数に引数を適用することを表している。規則2のことをラムダ抽象: lambda abstraction)といい、規則3のことを関数適用: application)という。関数適用は左結合であることと、ラムダ抽象はその後ろに続く全ての式を束縛することの2点をもってあいまいさが排除される場合は、括弧を省略してもよい。例えば、 ((λx. ((x x) x)) (λy. y)) はより簡単に (λx. x x x) λy. y と書ける。また、非形式的な説明で述べたようにMをラムダ式としたとき、λx. (λy. M)をλxy. Mと略記する。

ラムダ抽象によって束縛されていない変数を自由変数: free variable)という。式 λx. (x y) において、 y は自由変数である。ある変数の出現が自由出現であるかどうかは、より正確には以下のように帰納的に定義されている。

  1. ラムダ式 V が変数のとき、 V は自由出現である。
  2. ラムダ式 λV. E において、 E で自由出現している変数のうち V 以外のものが自由出現である。このとき、 E 中の変数 V はラムダに束縛されたという。
  3. ラムダ式 (E E′) において、 E での自由出現と E′ での自由出現の和が自由出現である。

ラムダ式の集合の上での同値関係(ここでは == と書くことにする)は、直感的には、2つのラムダ式が同じ関数を表していることである。この同値関係は以下で述べるα-変換とβ-簡約によって定義される。第3の規則としてη-変換と呼ばれる規則が導入されることもある。

α-変換

[編集]

アルファ変換の基本的なアイデアは、束縛変数の名前は重要ではない、ということにある。例えば、 λx. x と λy. y は同じ関数を表している。しかし、ことはそう単純ではない。ある束縛変数の名前を置換してもよいかどうかには、いくつかの規則が絡んでくる。例えば、ラムダ式 λx. λy. x 中の変数 xy に置き換えると、 λy. λy. y となるが、これは最初の式とはまったく異なるものを表すことになる。そこでまず準備として、変数 V, W と式 E に対して、 E 中の V の全ての自由出現を W に置き換えたものを

E[V := W]

と書くことにする。この元で、アルファ変換は

λV. E   →α   λW. E[V := W]

である。ただし、 EW が自由出現しておらず、かつ V を置換することにより E 中で新たに W が束縛されることがないときに限る。この規則によれば、式 λx. (λx. x) x が λy. (λx. x) y に変換されることがわかる。

β-簡約

[編集]

ベータ簡約(ベータ変換とも)の基本的なアイデアは、関数の適用である。ベータ簡約は以下の変換である。

((λV. E) E′)   →β   E[V := E′]

ただし、 E′ の代入によって E′ 中の自由変数が新たに束縛されることがないときに限る。

関係 == は、上の2つの規則を含む最小の同値関係(同値閉包)である。

ベータ簡約は、(同値関係ではなく)左辺から右辺への一方的な変換であると見ることもできる。ベータ簡約の余地のないラムダ式、つまり、 ((λV. E) E′) の形(β-redex)をどこにも持っていないラムダ式を正規形: normal form)であるという。

η-変換

[編集]

上に挙げた2つの規則の他に、第3の規則としてイータ変換が導入されることがある。イータ変換の基本的なアイデアは、関数の外延性である。ここでいう外延性とは、2つの関数が全ての引数に対して常に同じ値を返すようなとき、互いに同値であるとみなすという概念である。イータ変換は以下の変換である。

λV. E V   →η   E

ただし、 EV が自由出現しないときに限る。

この同値性は関数の外延性という概念によって以下のように示される。

もし全てのラムダ式 a に対して f a == g a であるならば、 a として f にも g にも自由出現しない変数 x をとることによって f x == g x であり、 λx. f x == λx. g x である。この等式にイータ変換をほどこすことによって f == g が得られる。これより、イータ変換を認めるならば関数の外延性が正当であることが示される。

逆に、関数の外延性を認めるとする。まず、全ての y に対してラムダ式 (λx. f x) y はベータ変換でき、 (λx. f x) y == f y となる。この同値関係は全ての y について成り立っているので、関数の外延性より λx. f x == f である。以上によって、関数の外延性を認めたときのイータ変換の正当性が示される。

諸概念のラムダ式での表現

[編集]

上で述べたように、ラムダ計算は計算可能な全ての関数を表現することができる。また、上では 2 + 3 のような算術をラムダ式の一部として用いた。 2 + 3 などは計算可能であるから、もちろんラムダ計算による表現が可能である。もちろん、 2 + 3 以外にも計算可能な全ての関数の表現が可能である。ここではそれらのうちの主なものを紹介する。

自然数と算術

[編集]

自然数をラムダ式で表現する方法はいくつか異なる手法が知られているが、その中でもっとも一般的なのはチャーチ数英語版: Church numerals)と呼ばれるもので、以下のように定義されている。

0 := λf x. x
1 := λf x. f x
2 := λf x. f (f x)
3 := λf x. f (f (f x))

以下同様である。直感的には、数 n はラムダ式では f という関数をもらってそれを n 回適用したものを返す関数である。つまり、チャーチ数は1引数関数を受け取り、1引数関数を返す高階関数である。(チャーチの提唱した元々のラムダ計算は、ラムダ式の引数が少なくとも一回は関数の本体に出現していなくてはならないことになっていた。そのため、その体系では上に挙げた 0 の定義は不可能である。)

上のチャーチ数の定義のもとで、後続(後者)を計算する関数、すなわち n を受け取って n + 1 を返す関数を定義することができる。それは以下のようになる。

SUCC := λn f x. f (n f x)

また、加算は以下のように定義できる。

PLUS := λm n f x. m f (n f x)

または単にSUCCを用いて、以下のように定義してもよい。

PLUS := λm n. m SUCC n

PLUS は2つの自然数をとり1つの自然数を返す関数である。この理解のためには例えば、 PLUS 2 3 == 5 であることを確認してみるとよいだろう。また、乗算は以下のように定義される。

MULT := λm n. m (PLUS n) 0

この定義は、 mn の乗算は、 0 に nm回加えることと等しい、ということを利用して作られている。もう少し短く、以下のように定義することもできる。

MULT := λm n f. m (n f)

正の整数 n の先行(前者)を計算する関数 PRED n = n − 1 は簡単ではなく、

PRED := λn f x. ng h. h (g f)) (λu. x) (λu. u)

もしくは

PRED := λn. ng k. (g 1) (λu. PLUS (g k) 1) k) (λv. 0) 0

と定義される。上の部分式 (g 1) (λu. PLUS (g k) 1) k は、 g(1) がゼロとなるとき k に評価され、そうでないときは g(k) + 1 に評価されることに注意せよ[1]

論理記号と述語

[編集]

TRUEFALSE といった真理値は慣習的に以下のように定義されることが多い。これらはチャーチ真理値英語版: Church booleans)とよばれている。

TRUE := λx y. x
FALSE := λx y. y
(この FALSE は前述のチャーチ数のゼロと同じ定義であることに注意せよ)

これらの真理値に対して論理記号を定義することができる。たとえば、以下のようなものがある。

AND := λp q. p q FALSE
OR := λp q. p TRUE q
NOT := λp. p FALSE TRUE
IFTHENELSE := λp x y. p x y

これらの記号を使った計算の例を挙げる。

AND TRUE FALSE
= (λp q. p q FALSE) TRUE FALSEβ TRUE FALSE FALSE
= (λx y. x) FALSE FALSEβ FALSE

以上より、 AND TRUE FALSEFALSE と等しいことがわかる。

述語」とは、真理値を返す関数のことである。計算論において最も基本的な述語は ISZERO で、これは引数がチャーチ数の 0であった場合には TRUE を、そうでなければ FALSE を返す関数であり、以下のように定義できる。

ISZERO := λn. nx. FALSE) TRUE

[編集]

(2つ組の)順序対のデータ型は、 TRUE および FALSE を用いて定義することができる。これらはチャーチ対英語版: Church pairs)とよばれている。

CONS := λs b f. f s b
CAR := λp. p TRUE
CDR := λp. p FALSE

リンク型のリスト構造は、空リストのために特定の予約された値(例えば FALSE )を用い、リストをその先頭要素と後続リストの CONS 対として表現することによって実現できる。

リスト

[編集]

再帰

[編集]

再帰とは自分自身を関数として使用することで、ラムダ計算では表面上は再帰操作は許されていないように見える。しかし少し工夫することによってラムダ計算でも再帰を実現できる。例として階乗を計算する関数 f(n) を考えてみよう。この関数は再帰的に以下のように定義できる。

f(n) := 1, if n = 0; and n × f(n − 1), if n > 0

ラムダ計算では自分自身を含む関数は定義できない。この問題を解決するためにまず、 f を引数にとり n を引数にとる関数を返すg という関数を考える。

g := λf n. (1, if n = 0; and n × f(n − 1), if n > 0)

関数 g は 1 か n × f(n − 1) を返すような関数を返す。上述の ISZERO や算術、論理記号の定義を用いれば、この関数 g はラムダ式で定義することができる。

しかし、これでは g 自身はまだ再帰的ではない。 g を用いて再帰的な階乗関数を作り出すためには、 g に対して引数 f として渡されている関数が、ある性質を持つ必要がある。すなわち、この f を展開すると関数 g がある一つの引数を伴った形になり、さらにその g への引数は先ほどf として渡された関数に再びなる必要がある。

この性質は言い換えると、 fg ( f )に展開される必要があるということだ。この g の呼び出しは先ほどの階乗関数に展開され、再帰の段階を一段降りる計算をしている。この展開において、関数 f が再度出現する。そして、この関数 f は再度 g ( f )に展開され、再帰が続いていく。この f = g ( f )となるような関数は、 g不動点と呼ばれる。そして、この不動点は不動点コンビネータとして知られるものによってラムダ計算で表現することが出来る。この不動点コンビネータは Y と表される -- Yコンビネータ:

Y = λg. (λx. g (x x)) (λx. g (x x))

ラムダ計算では、 Y gg の不動点となる。つまり、 g (Y g) == Y g となる。このもとで、 n の階乗を計算するには単に g (Y g) n を呼び出せばよい。ここで、 n は上述したチャーチ数である。

n = 5 として、評価の例を見てみよう。

n.(1, if n = 0; and n·((Y g)(n − 1)), if n > 0)) 5
1, if 5 = 0; and 5·(g(Y g)(5 − 1)), if 5 > 0
5·(g(Y g) 4)
5·(λn. (1, if n = 0; and n·((Y g)(n − 1)), if n > 0) 4)
5·(1, if 4 = 0; and 4·(g(Y g)(4 − 1)), if 4 > 0)
5·(4·(g(Y g) 3))
5·(4·(λ n. (1, if n = 0; and n·((Y g)(n− 1)), if n > 0) 3))
5·(4·(1, if 3 = 0; and 3·(g(Y g)(3 − 1)), if 3 > 0))
5·(4·(3·(g(Y g) 2)))
...

アルゴリズムの構造が再帰的に評価されているのがわかるだろう。再帰的に定義される関数は全て他の適当な関数の不動点となっているため、 Y を用いることで全ての再帰的な関数をラムダ式で表現することができる。たとえば、自然数に対する除算、乗算や比較述語を再帰を用いてよりきれいに定義することができる。

計算可能性とラムダ計算

[編集]

自然数から自然数への関数 F: NN計算可能であるということは、全ての自然数の対 X, Y に対して F(X) = Yf x == y が同値となるようなラムダ式 f が存在すること、と定義することができる。ここで、 x, y はそれぞれ X, Y に対応するチャーチ数によるラムダ式である。この定義は、計算可能性を定義する多くの方法のうちのひとつである。より詳しくは、チャーチ-チューリングの提唱の項を見よ。

同値性の決定不可能性

[編集]

2つのラムダ式を入力とし、それらが同値であるかどうかを判定するアルゴリズムは存在しない。これは決定不可能性が示された歴史的に最初の問題である。ここで使われる「アルゴリズム」という言葉も、もちろんきちんと定義されなければならない。チャーチは自身の証明の中で帰納的関数をその定義に用いたが、現在ではこれは適切なその他のアルゴリズムの定義と等価であることが知られている。

チャーチの証明ではこの問題を、あたえられたラムダ式に正規形が存在するかどうかという問題に帰している。正規形とは、それ以上簡約のできない同値なラムダ式のことである。チャーチの証明ではまず、この問題が決定可能である、つまり、ラムダ式で表現可能であると仮定する。クリーネによる結果とゲーデル数のラムダ式表現を用いることによってチャーチは、対角線論法によりパラドキシカルなラムダ式 e を構成した。この e を、それ自身を表すゲーデル数に適用すると矛盾が導かれる。

詳しくいえば次のようである。まず を正規形の存在性を判定するラムダ式とする。 を2つのラムダ式のゲーデル数から、それらを適用してできるラムダ式を計算する関数を表すラムダ式、 を自然数からそれを表すラムダ式の表現のゲーデル数を求める関数を表すラムダ式とする。すなわち、

が成り立つ。ここで はラムダ式 のゲーデル数を表すラムダ式の表現である。 いま、ラムダ式

と定める。ここで は正規形を持たないラムダ式 である。自己適用 を計算すると次のようになる。

もし が正規形を持つならば、 にベータ簡約される。するとチャーチ・ロッサーの定理より、 と共通のラムダ式にベータ簡約できるから、 は正規形を持つ。これは矛盾。したがって は正規形を持たない。すると にベータ簡約されることになる。ラムダ式 は正規形であるので、やはり矛盾。したがって のようなラムダ式は存在しない。

チャーチ・ロッサー性

[編集]

一般にラムダ式の中にβ-変換できる部分式が複数ある場合、どこから評価を行うかによって評価の経過は複数存在する。それらの複数の経過からさらに評価することによって、同じラムダ式を得られる性質をチャーチ・ロッサー性、もしくは合流性と呼ぶ(チャーチ・ロッサーの定理)。また、あるラムダ式から一回のβ-簡約によって得られた二つのラムダ式が、同じラムダ式にβ-変換されるという性質は弱チャーチ・ロッサー性と呼ばれる。チャーチ・ロッサー性を持つ体系は弱チャーチ・ロッサー性も持つが、逆は必ずしもなりたたない。

チャーチ・ロッサー性は本稿で取り扱っている型無しラムダ計算の体系では成立することが知られている。しかしその他の体系、例えば型を付けて拡張されたラムダ計算の体系などに関しては、必ずしも成り立つとは限らない。

停止性

[編集]

β-変換は停止しない(無限ループに陥る)場合がある。例えば次の式を適用する場合には停止しない。

x. x x) (λx. x x) →βx. x x) (λx. x x) →β

ある種のラムダ計算の体系では、任意のラムダ式に対してβ-変換の停止性が保証されていることがある。どのような順序でβ-変換を行ったとしてもβ-変換が停止する性質を強正規化性といい、β-変換の順序を上手く選んだ場合にβ-変換が停止する性質を弱正規化性という。チャーチ・ロッサー性を満たし、かつ停止性を持つラムダ計算の体系では、ラムダ式をどのような順序で評価しても必ず同じ結果になることがわかる。

強正規化的であり、かつ弱チャーチ・ロッサー性を持つラムダ計算の体系はチャーチ・ロッサー性を持つ(ニューマンの補題英語版)。

型無しラムダ計算の体系では、ある式の停止性を判断する事は決定不能であることが証明されている。

ラムダ計算とプログラミング言語

[編集]

ピーター・ランディン英語版は1965年に発表したA Correspondence between ALGOL 60 and Church's Lambda-notationにおいて、ラムダ計算が手続的な抽象化と手続き(サブプログラム)の適用のしくみを提供しているがために、多くのプログラミング言語がラムダ計算にその基礎を置いているとみることができるとしている。

ラムダ計算をコンピュータ上に実装するには、関数を第一級オブジェクトとして取り扱う必要があり、これはスタック・ベースのプログラミング言語においては問題となってくる。これはFunarg問題として知られている。

ラムダ計算と最も密接な関係をもつプログラミング言語は関数型言語と呼ばれる諸言語で、本質的にはいくつかの定数データ型を用いてラムダ計算を実装している。Lispでは関数の定義にラムダ記法の一変形を用いており、さらに、純Lispと呼ばれるLispのサブセットはラムダ計算と真に等価になっている。

関数を第一級オブジェクトとして扱えるのは関数型言語だけというわけではない。Pascalなど、多くの命令型言語ではある関数を他の関数の引数として与える操作が許されている。CC++では関数を指すポインタクラス型関数オブジェクトを用いて同じことが実現できる。このような機能はサブ関数が明示的に書かれている場合にのみ用いることができ、したがってこの機能がそのまま高階関数をサポートしていることにはならない。いくつかの手続的なオブジェクト指向言語では関数を任意の階数に書くことができる。Smalltalkや、より最近の言語ではEiffel(エージェント)やC#デリゲート)などで用意されている機能がそれである。例えば、Eiffelのインライン・エージェントの機能を用いた以下のコード

agent (x: REAL): REAL do Result := x * x end 

はラムダ式 λx. x * x (値呼び出し)に相当するオブジェクトを表している。このオブジェクトは他のあらゆるオブジェクトと同様に、変数に代入したり関数に渡したりすることができる。変数 square の値が上のエージェントのオブジェクトであるとすれば、 square に値 a を適用した結果(β-簡約)は square.item([a]) と書ける。ただしここでの引数はタプルであるとみなされる。

並行性

[編集]

ラムダ計算のチャーチ・ロッサー性は、評価(β-簡約)をどの順序で行っても、さらには同時に(並行に)行ってもよいことを意味している。(より詳しくいえば、ラムダ計算は参照透過である。)このため、ラムダ計算を用いて種々の非決定的評価戦略をモデル化することができる。並列性並行性をモデル化するためのより強力な手法として、CSPCCSパイ計算アンビエント計算などのプロセス計算がある。

参考資料

[編集]
  • 計算論 計算可能性とラムダ計算 コンピュータサイエンス大学講座 高橋 正子 (著) 近代科学社 ISBN 4764901846 (1991)
  • ハロルド・エイブルソン、ジェラルド・ジェイ・サスマン、ジュリー・サスマン共著『計算機プログラムの構造と解釈 第二版』和田英一訳、ピアソンエデュケーション、2000、ISBN 4-8947-1163-X

この記事は2008年11月1日以前にFree On-line Dictionary of Computingから取得した項目の資料を元に、GFDL バージョン1.3以降の「RELICENSING」(再ライセンス) 条件に基づいて組み込まれている。

脚注

[編集]
  1. ^ チャーチ数とpred関数”. kimiyuki.net. 2018年10月6日閲覧。

関連項目

[編集]