回答:
簡単に言えば、ラムダは名前のない関数、または無名関数です。実行可能コードの小さな断片。変数のように渡すことができます。JavaScriptの場合:
function () {}; // very simple
これらのラムダのいくつかの用途を見てみましょう。
ラムダは、定型コードを抽象化するために使用できます。たとえばループ。私たちは一日中、書き込みfor
とwhile
ループに慣れています。しかし、これは書かれていないコードです。ループの最も重要な部分であるループ内のコードを抽出し、残りを抽象化できます。
for (var i=0; i<array.length; i++) {
// do what something useful with array[i]
}
forEach
配列オブジェクトのを使用すると、次のようになります。
array.forEach(function (element, index) {
// do something useful with element
// element is the equivalent of array[i] from above
});
上記の抽象化はそれほど有用ではないかもしれませんが、forEach
はるかに有用なタスクを実行する、などの他の高次関数があります。例filter
:
var numbers = [1, 2, 3, 4];
var even = [];
// keep all even numbers from above array
for (var i=0; i<numbers.length; i++) {
if (numbers[i] % 2 === 0) {
even.push(numbers[i]);
}
}
alert(even);
// Using the filter method
even = [1, 2, 3, 4].filter(function (number) {
return number % 2 === 0;
});
alert(even);
イベントの概念が利用可能な一部の環境では、ラムダを使用して、ある時点で発生する可能性のあるイベントに応答できます。
window.onload = function () {
alert("Loaded");
};
window.setTimeout(function () {
alert("Code executed after 2 seconds.");
}, 2000);
これは他の方法で行うこともできますが、かなり冗長です。たとえば、JavaにはRunnable
インターフェースがあります。
この時点まで、構文糖度機能にはラムダのみを使用していました。しかし、ラムダの方がはるかに便利な場合があります。たとえば、ラムダを返す関数があるとします。戻り値をキャッシュしたい関数があるとしましょう。
var users = [];
var getUser = function (name) {
if (! users[name]) {
// expensive operations to get a user. Ajax for example
users[name] = user_from_ajax;
}
return users[name];
};
後で、同じような機能があることに気付くでしょう:
var photos = [];
var getPhoto = function (name) {
if (! photo[name]) {
// expensive operations to get a user. Ajax for example
photos[name] = photo_from_ajax;
}
return photos[name];
};
そこには明らかにパターンがあるので、抽象化してみましょう。メモ化を使ってみましょう。
/**
* @param {Array} store Data structure in which we cache lambda's return values
* @param {Function} lambda
* @return {Function} A function that caches the result of calling the lambda param
*/
var memoize = function (store, lambda) {
// return a new lambda
return function (name) {
if (! store[name]) {
// Execute the lambda and cache the result
store[name] = lambda(name);
}
return store[name];
};
};
var getUsers = memoize([], function (name) {
// expensive operations to get a user. Ajax for example
});
var getPhotos = memoize([], function (name) {
// expensive operations to get a photo. Ajax for example
});
ご覧のとおり、ラムダを使用することで、キャッシング/メモ化ロジックを抽象化することができました。他の例でいくつかの回避策があった場合、この特定の問題は他の手法を使用してもほとんど解決されないと思います。重要なボイラープレートコードを1か所に抽出できました。言うまでもなく、users
とphotos
グローバル変数を削除しました。
あなたのプロフィールを見ると、あなたはほとんどPythonユーザーであることがわかります。上記のパターンの場合、Pythonにはデコレーターの概念があります。ネット上には、メモ化デコレータの例がたくさんあります。唯一の違いは、Pythonではデコレータ関数内に名前付きのネストされた関数がある可能性が最も高いことです。その理由は、Pythonが単一式のラムダのみをサポートしているためです。しかし、コンセプトは同じです。
Pythonラムダの使用例として。偶数をフィルター処理した上記のコードは、次のようにPythonで表すことができます。
filter(lambda x: x % 2 == 0, [1, 2, 3, 4])
とにかく、ラムダはクロージャーなしではそれほど強力ではありません。クロージャはラムダの概念を非常に強力にするものです。私のメモ化の例では、クロージャーを使用してパラメーターの周りにクロージャーを作成しましたstore
。このようにして、memoize
関数が結果(ラムダ)を返した後でも、そのパラメーターにアクセスできます。
「ラムダ」という用語は、匿名関数、通常はクロージャーを指すために使用されます。コードを不必要に肥大化させることなく、他の関数を使用する関数を記述できるので、これらは便利です。たとえば、Rubyでは:
(1..100).select {|num| num % 2 == 0}
これにより、1〜100の偶数を含む配列が作成されます。明示的なループを記述する必要はありません。selectメソッドは、値をテストするために使用する関数を受け取るため、必要なのはカスタムロジックだけです。これにより、ほとんど手間やオーバーヘッドをかけずにメソッドを大幅にカスタマイズできます。基本的に、小さな関数から関数を作成できます。
それは彼らができることの簡単な例にすぎません。関数をデータとして渡す機能は非常に強力であり、関数型言語のプログラマーは日常的にそれを使用していくつかの本当に驚くべきことを行います。
「ラムダ」は少なすぎるかもしれません。ラムダ計算を見てください。関数型プログラミングで役立ちます。
また、関数型プログラミングは、もう1つのプログラミングパラダイムです(手続き型またはオブジェクト指向など)。
.NETのラムダは、「構文糖」と呼ばれることがよくあります。それらは機能に直接影響しませんが、人々が言語を使いやすくします。
それらを使用する力を理解すると、デリゲート/匿名メソッドを使用する古いスタイルの方法と比較して、少ないコードを書くことがわかるでしょう。
ドブスジャーナル博士には、ラムダ式を紹介する有用な記事があります(C ++のコンテキスト内ですが、原則を任意の言語に適用できます)。
記事が言うように:「ラムダ式は、個別のクラス/関数定義を必要としない非常にコンパクトな式です。」
したがって、DDJのリスト1と2の例を書く代わりに使用します。
std::for_each( vec.begin(), vec.end(), print_to_stream<std::string>(std::cout));
次のような個別のクラス定義が必要です。
template <typename T, typename Stream> class print_to_stream_t {
Stream& stream_;
public:
print_to_stream_t(Stream& s):stream_(s) {}
void operator()(const T& t) const {
stream_ << t;
}
};
template <typename T,typename Stream>
print_to_stream_t<T,Stream> print_to_stream(Stream& s) {
return print_to_stream_t<T,Stream>(s);
}
Boostラムダライブラリを使用すると、次のようになります。
std::for_each(vec.begin(),vec.end(),std::cout << _1);
これはインラインで定義を保持します。
また、ラムダ式のいくつかのアプリケーションについても説明します。
DDJ記事の重要なポイントは「通常、ラムダ式は、呼び出しサイトで小さくて複雑すぎない関数が必要な場合に使用されます。関数が自明ではない場合、ラムダ式ではなく通常の関数または関数オブジェクト。」
関数ポインター、デリゲート、ストラテジー、またはオブザーバーパターン/イベント処理を使用する関数/メソッドを使用していて、「この関数全体を1回だけ使用するために記述している-このメソッドに渡すため」 ;コードを乱雑にするのではなく、適切な場所に記述できればいいのにと思います。 "-ここでLambda関数を使用できます。この構造をサポートする言語は、通常、特にリスト(ファーストクラス関数および高次関数)の操作に関して、関数をパラメーターとして渡すという概念を大幅に利用します。これは、計算のためにメモリの変更ではなく関数の構成に依存する関数型言語に特に当てはまります。場合によっては(Pythonなどの言語)、
「ラムダ」という言葉は、コンピュータサイエンスのタイプが、コンピュータサイエンスの学位を取得するよりも数学やロジックでトレーニングされる可能性が高かった時代からの用語です。それらの中には、「関数型プログラミング」と呼ばれるパラダイムを作り上げました。これは、命令型とはかなり異なり、非常に強力でもあります。AFAIKは、この用語が使用されるようになった環境です。
数学者と論理学者は奇妙な言葉を使うことに慣れています。
「ラムダ」は本当に難解に聞こえます-まるでそれらが非常に奇妙で特別なものであるかのように。実際、Webブラウザーアプリケーション用のJavaScriptを記述し、 "var foo = function(){...}"イディオムを使用する場合、ずっとラムダ関数を使用してきました。
「ラムダ式は、式とステートメントを含むことができる無名関数であり、デリゲートまたは式ツリータイプを作成するために使用できます。
すべてのラムダ式は、ラムダ演算子=>を使用します。これは、「移動先」として読み取られます。ラムダ演算子の左側は入力パラメーター(ある場合)を指定し、右側は式またはステートメントブロックを保持します。ラムダ式x => x * xは、「xはxのx倍になります」と読み取られます。
MSDNから
Javaに慣れている方は、この機能をJava 7に追加するためのさまざまな提案があったため、過去2か月でラムダまたはクロージャーについて多くのことを耳にしました。提案の1つはNeal Gafterからのものであり、ここで詳細に説明されています:javac.info。これは、ユースケースと利点(特に内部クラスに対する利点)を理解するのに役立ちました
Javaラムダの場合、これは良い出発点になるかもしれません:
http://rodrigouchoa.wordpress.com/2014/09/10/java-8-lambda-expressions-tutorial/