ラムダ
2004年9月8日
動的言語への関心の高まりとともに、ラムダ(クロージャ、無名関数、ブロックとも呼ばれる)と呼ばれるプログラミング概念に遭遇する人が増えています。C/C++/Java/C#などの言語背景を持つ人はラムダを扱ったことがなく、そのためそれが何かよく分かりません。ここでは簡単な説明をします。ラムダを扱ったプログラミング経験のある人は、それほど面白くはないでしょう。
ラムダは古くから存在します。私が初めて本格的に遭遇したのはSmalltalkで、そこでブロックと呼ばれていました。Lispでは広く使われています。Rubyスクリプト言語にも存在し、多くのRubyistがRubyをスクリプトに使用することを好む大きな理由の一つとなっています。
基本的に、ラムダは関数呼び出しに引数として渡すことができるコードブロックです。簡単な例で説明します。従業員オブジェクトのリストがあり、IsManagerプロパティで判断するマネージャーである従業員のリストが必要だとします。C#を使用する場合、おそらく次のように記述します。
public static IList Managers(IList emps) { IList result = new ArrayList(); foreach(Employee e in emps) if (e.IsManager) result.Add(e); return result; }
ラムダを持つ言語、この場合はRubyでは、次のように記述します。
def managers(emps) return emps.select {|e| e.isManager} end
基本的に、selectはRubyの集合クラスで定義されたメソッドです。コードブロック、つまりラムダを引数として受け取ります。Rubyでは、そのコードブロックを中括弧で記述します(唯一の方法ではありません)。コードブロックが引数を受け取る場合は、縦棒の間に宣言します。selectが行うことは、入力配列を反復処理し、各要素でコードブロックを実行し、ブロックがtrueと評価された要素の配列を返すことです。
Cプログラマであれば、「関数ポインタでできる」と思うでしょうし、Javaプログラマであれば、「匿名内部クラスでできる」と思うでしょう。これらのメカニズムはラムダに似ていますが、2つの重要な違いがあります。
1つ目は形式的な違いで、ラムダは通常クロージャを定義します。つまり、定義された時点で見える変数を参照できるということです。次のメソッドを考えてみましょう。
def highPaid(emps) threshold = 150 return emps.select {|e| e.salary > threshold} end
selectブロック内のコードは、囲んでいるメソッドで定義されたローカル変数を参照していることに注目してください。ラムダ以外の、真のクロージャを持たない言語における代替手段の多くは、そのようなことができません。ラムダを使用すると、さらに興味深いことができます。次の関数を見てください。
def paidMore(amount) return lambda {|e| e.salary > amount} end
この関数はラムダを返し、実際には、その動作が渡された引数によって変わるラムダを返します。そのような関数を生成して変数に代入することができます。
highPaid = paidMore(150)
変数`highPaid`には、テスト対象のオブジェクトの給与が150より大きいかどうかを返すコードブロックが含まれています。次のように使用できます。
john = Employee.new john.salary = 200 print highPaid.call(john)
`highPaid.call(john)`という式は、前に定義した`e.salary > amount`コードを呼び出します。そのコード内の`amount`変数は、procオブジェクトを作成したときに渡した150にバインドされています。print呼び出しを行う際にその150の値がスコープ外になっても、バインディングは残ります。
したがって、ラムダに関する最初の重要な点は、通常クロージャを作成することです。つまり、コードブロックとそのコードブロックが由来する環境へのバインディングです。クロージャを作成しないラムダも存在しますが、そのようなものはあまり役に立たず、あまり一般的ではありません。そのため、クロージャはラムダの代替用語としてよく使われます。[1][2]
2つ目の違いは、定義された形式的な違いというよりは、実際にはそれと同じくらい、もしくはそれ以上に重要です。ラムダをサポートする言語では、非常に少ない構文でラムダを定義できます。これは重要ではないように思えるかもしれませんが、私はそれが重要だと信じています。それは、それらを頻繁に使用することを自然にするための鍵です。Lisp、Smalltalk、またはRubyのコードを見てみると、ラムダが至る所に存在していることがわかります。他の言語の同様の構造よりもはるかに頻繁に使用されています。ローカル変数にバインドする機能もその一部ですが、最大の理由は、それらを使用するための表記がシンプルで明確であることだと思います。
良い例として、元SmalltalkerがJavaを使い始めたときに起こったことがあります。当初、私を含め多くの人が、Smalltalkでブロックで行っていた多くのことを匿名内部クラスを使用して実験しました。しかし、結果として得られたコードはあまりにも複雑で醜かったので、私たちは諦めました。
私はRubyでラムダをたくさん使いますが、明示的に作成して渡すことはあまりありません。ほとんどの場合、私のラムダの使用は、前に示した`select`メソッドのようなコレクションパイプラインに基づいています。もう1つの一般的な用途は、「実行周りのメソッド」です。ファイルの処理などです。
File.open(filename) {|f| doSomethingWithFile(f)}
ここでは、openメソッドがファイルをオープンし、指定されたブロックを実行し、その後閉じます。これは、トランザクション(コミットまたはロールバックを忘れないようにする)や、最後に何かをすることを忘れてはならないあらゆる状況で非常に便利なイディオムです。私はこれをXML変換ルーチンで広く使用しています。
このようなラムダの使用は、Lispや関数型プログラミングの世界の人々が実際に行うことよりもはるかに少ないです。しかし、私の限られた使用でも、ラムダを持たない言語でプログラミングするときは、非常に不便を感じます。最初に遭遇したときは些細なように思えるものの、すぐに好きになるものの1つです。
Neal Gafterは、クロージャの歴史に関する優れた投稿をしています。Vadim Nasardinovは、Guy SteeleによるJavaにおけるクロージャの興味深い歴史的情報を教えてくれました。
注記
1: 2004年にこのblikiエントリを最初に公開したとき、私はこれらの言語機能を指して「クロージャ」という用語を使用しました。当時「クロージャ」という用語は頻繁に使用されていました。その後、「ラムダ」という用語がより一般的になったため、このblikiエントリをそれに合わせて変更しました。
2: Javaの匿名内部クラスはローカル変数にアクセスできますが、finalである場合のみです。