誰かが説明できますか?私はそれらの背後にある基本的な概念を理解していますが、それらが同じ意味で使用されているのをよく見、混乱しています。
ここに来たので、通常の関数とどう違うのですか?
誰かが説明できますか?私はそれらの背後にある基本的な概念を理解していますが、それらが同じ意味で使用されているのをよく見、混乱しています。
ここに来たので、通常の関数とどう違うのですか?
回答:
ラムダはなし名前で定義された関数-ちょうど無名関数です。Schemeなどの一部の言語では、名前付き関数と同等です。実際、関数定義は、ラムダを変数に内部的にバインドするように書き直されています。Pythonのような他の言語では、それらの間にいくつかの(かなり不必要な)違いがありますが、それ以外は同じように動作します。
クロージャは任意の関数である上に閉じた環境、それが定義されました。つまり、パラメーターリストにない変数にアクセスできます。例:
def func(): return h
def anotherfunc(h):
return func()
- が未定義であるため、環境が閉じfunc
ないため、エラーが発生します。地球環境のみを閉じます。これは動作します:anotherfunc
h
func
def anotherfunc(h):
def func(): return h
return func()
ここで、func
はanotherfunc
、およびpython 2.3以降(またはこのようないくつかの数)で定義されているため、クロージャがほぼ正しくなった場合(突然変異はまだ機能しません)、これはの環境を閉じて anotherfunc
内部の変数にアクセスできることを意味しますそれ。Python 3.1以降では、nonlocal
キーワードを使用するとミューテーションも機能します。
もう1つの重要な点func
は、anotherfunc
で評価されなくなった場合でも、の環境は引き続き閉じられanotherfunc
ます。このコードも機能します:
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
これは10を印刷します。
お気づきのように、これはラムダとは何の関係もありません。これらは2つの異なる(関連していますが)概念です。
ここのこのStackOverflowの質問への回答でさえ、ラムダとクロージャーに関して多くの混乱があります。特定のプログラミング言語または他の無知なプログラマーの実践からクロージャーについて学んだランダムなプログラマーに尋ねる代わりに、ソース(すべてが始まったところ)への旅に出ます。そしてラムダとクロージャは、最初の電子コンピュータが存在する前の30年代にAlonzo Churchによって発明されたラムダ計算から来ているので、これが私が話している情報源です。
ラムダ計算は世界で最も単純なプログラミング言語です。あなたがそれにできる唯一のこと:►
f x
ます。f
で、は関数でx
あり、唯一のパラメーターです)λ
(ラムダ)、記号名(例x
:).
、式の前にドットを付加することによって行われます。次に、式を1つのパラメータを必要とする関数に変換します。λx.x+2
式x+2
を受け取り、x
この式のシンボルがバインドされた変数であることを通知します。これは、パラメーターとして指定した値で置き換えることができます。(λx.x+2) 7
。次に、式(この場合はリテラル値)7
が、適用されたラムダのx
部分式x+2
のように置き換えられるため、が得られます7+2
。これは9
、一般的な算術規則によってに縮小されます。そこで、謎の1つを解決しました
。lambdaは、上の例の無名関数λx.x+2
です。
function(x) { return x+2; }
そして、あなたはすぐにこのようないくつかのパラメータにそれを適用することができます:
(function(x) { return x+2; })(7)
または、この無名関数(lambda)をいくつかの変数に格納できます。
var f = function(x) { return x+2; }
これは事実上それに名前を付けf
、それを参照して後で何度も呼び出すことができるようにします。例:
alert( f(7) + f(10) ); // should print 21 in the message box
しかし、あなたはそれに名前を付ける必要はありませんでした。あなたはそれをすぐに呼び出すことができます:
alert( function(x) { return x+2; } (7) ); // should print 9 in the message box
LISPでは、ラムダは次のように作成されます。
(lambda (x) (+ x 2))
そして、そのようなラムダをパラメータにすぐに適用することで呼び出すことができます:
( (lambda (x) (+ x 2)) 7 )
私が言ったように、ラムダ抽象化が行うことは、その副次式でシンボルをバインドすることです。それにより、それは置換可能なパラメータになります。このようなシンボルは、バインドと呼ばれます。しかし、式に他の記号がある場合はどうなりますか?次に例を示しますλx.x/y+2
。この式では、シンボルx
はそのλx.
前のラムダ抽象化によってバインドされています。しかし、もう1つの記号y
は拘束されていません。自由です。それが何でどこから来たのかわからないので、それが何を意味し、どのような値を表しているのかわからないため、意味を理解するまでその式を評価することはできませんy
。
実際、同じことが他の2つの記号、2
およびにも当てはまり+
ます。これらの2つの記号に慣れ親しんでいるだけで、コンピューターがそれらを知らないことを忘れてしまいます。たとえば、ライブラリや言語自体のどこかに定義することで、それらの意味を伝える必要があります。
フリーシンボルは、式とは別に、「環境」と呼ばれる「周囲のコンテキスト」で定義されていると考えることができます。環境は、この式の一部であるより大きな式かもしれません(Qui-Gon Jinnが言ったように、「常により大きな魚がいます」;))、またはいくつかのライブラリー、または言語自体(プリミティブとして)。
これにより、ラムダ式を2つのカテゴリに分割できます。
これらのフリーシンボルをいくつかの値(数値、文字列、匿名関数、別名ラムダなど)にバインドすることで定義する環境を提供することにより、オープンラムダ式を閉じることができます。
そして、ここでクロージャ部分が来ます:ラムダ式
のクロージャは、この式のフリーシンボルに値を与え、もはやフリーではないようにする、外部コンテキスト(環境)で定義されたこの特定のシンボルのセットです。それはターンオープンに、まだいくつかの「未定義」の自由のシンボルが含まれているラムダ式を、閉じもはや任意の自由のシンボルを持っていないもの、。
たとえば、次のラムダ式を持っている場合:λx.x/y+2
、シンボルはx
シンボルながら、バインドされているy
自由である、したがって、式がありopen
、あなたが何を言っていない限り、評価することはできませんy
(と同じで手段+
と2
も自由です、)。しかし、次のような環境もあるとします。
{ y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5 }
この環境当社ラムダ式から全ての「未定義」(無料)のシンボルのための消耗品の定義(y
、+
、2
)、およびいくつかの余分な記号(q
、w
)。定義する必要があるシンボルは、環境のこのサブセットです。
{ y: 3,
+: [built-in addition],
2: [built-in number] }
そしてこれはまさにラムダ式の閉鎖です:>
つまり、開いているラムダ式を閉じます。これは、名前のクロージャが最初に由来した場所であり、これが、このスレッドでの非常に多くの人々の回答がまったく正しくない理由です:P
それは、Sun / Oracle、Microsoft、Googleなどの企業マーケットイドが非難されるべきです。それは、言語(Java、C#、Goなど)でこれらの構成体を呼んだためです。彼らはしばしばラムダであるはずのものを「閉鎖」と呼びます。または、「クロージャ」を、字句スコープの実装に使用した特定の手法、つまり、関数がその定義時に外部スコープで定義された変数にアクセスできるという事実を呼び出します。多くの場合、関数はこれらの変数を「囲む」、つまり、変数をデータ構造にキャプチャして、外部関数の実行終了後に破棄されないようにします。しかし、これは単なる事後の「民俗語の語源」とマーケティングを作り上げたものであり、混乱を招くだけです。
そして、彼らが言うことには常に少しの真実があり、それをあなたがそれを偽として簡単に却下することができないという事実のために、それはさらに悪いです:P説明させてください:
ラムダをファーストクラスのシチズンとして使用する言語を実装する場合は、周囲のコンテキストで定義されたシンボルを使用できるようにする必要があります(つまり、ラムダで自由変数を使用します)。そして、これらのシンボルは、周囲の関数が戻ったときでも存在している必要があります。問題は、これらのシンボルが関数のローカルストレージ(通常はコールスタック上)にバインドされていることです。これは、関数が戻るときに存在しなくなります。したがって、ラムダが期待どおりに機能するためには、外部コンテキストからこれらのすべての自由変数を何らかの方法で「キャプチャ」し、外部コンテキストがなくなった場合でも後で保存する必要があります。つまり、閉鎖を見つける必要がありますラムダ(それが使用するこれらすべての外部変数)を別の場所に保存します(コピーを作成するか、スタック用以外の場所に前もってそれらのスペースを準備します)。この目標を達成するために使用する実際の方法は、言語の「実装の詳細」です。ここで重要なのは、どこかに保存する必要があるラムダの環境からのフリー変数のセットであるクロージャーです。
「クロージャ」自体としてクロージャを実装するために、言語の実装で使用する実際のデータ構造の呼び出しを開始するのにそれほど時間はかかりませんでした。構造は通常、次のようになります。
Closure {
[pointer to the lambda function's machine code],
[pointer to the lambda function's environment]
}
そして、これらのデータ構造はパラメーターとして他の関数に渡され、関数から返され、変数に格納されてラムダを表し、それらの囲い込み環境やそのコンテキストで実行されるマシンコードにアクセスできるようにします。ただし、これはクロージャー自体を実装する方法ではなく、クロージャーを実装する方法の1つにすぎません。
上記で説明したように、ラムダ式のクロージャは、そのラムダ式に含まれる自由変数に値を与える環境内の定義のサブセットであり、式を効果的に閉じます(まだ評価できない開いたラムダ式を、閉じ、それに含まれるすべてのシンボルが現在定義されているので、その後、評価することができるラムダ式)。
それ以外のものは、これらの概念の本当のルーツを知らない、プログラマーと言語ベンダーの「カーゴカルト」と「ボードゥーマジック」にすぎません。
それがあなたの質問に答えてくれることを願っています。ただし、フォローアップの質問がある場合は、コメントでお気軽に質問してください。詳しく説明します。
ほとんどの人が関数について考えるとき、彼らは名前付き関数について考えます:
function foo() { return "This string is returned from the 'foo' function"; }
もちろん、これらは名前で呼び出されます。
foo(); //returns the string above
ラムダ式、あなたが持つことができ、匿名の機能を:
@foo = lambda() {return "This is returned from a function without a name";}
上記の例では、割り当てられた変数を通じてラムダを呼び出すことができます。
foo();
ただし、匿名関数を変数に割り当てるよりも便利ですが、それらをより高次の関数(つまり、他の関数を受け入れ/返す関数)との間で受け渡します。これらの多くの場合、関数の名前を付ける必要はありません。
function filter(list, predicate)
{ @filteredList = [];
for-each (@x in list) if (predicate(x)) filteredList.add(x);
return filteredList;
}
//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)});
クロージャは、名前付きまたは匿名関数が、それの機能が定義されているスコープ内の変数「オーバー閉じ」、すなわち、閉鎖はまだで使用されているすべての外の変数と環境を指しますときなどとして知られていることも閉鎖自体。名前付きのクロージャは次のとおりです。
@x = 0;
function incrementX() { x = x + 1;}
incrementX(); // x now equals 1
それは多くのようには思えませんが、これがすべて別の関数にありincrementX
、外部関数に渡された場合はどうなりますか?
function foo()
{ @x = 0;
function incrementX()
{ x = x + 1;
return x;
}
return incrementX;
}
@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)
これは、関数型プログラミングでステートフルオブジェクトを取得する方法です。「incrementX」という名前は必要ないため、この場合はラムダを使用できます。
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
すべてのクロージャーがラムダであるわけではなく、すべてのラムダがクロージャーであるとは限りません。どちらも関数ですが、必ずしも私たちが慣れ親しんでいる方法ではありません。
ラムダは基本的に、関数を宣言する標準的な方法ではなく、インラインで定義される関数です。ラムダはオブジェクトとして頻繁に渡されます。
クロージャーは、その外部のフィールドを参照することにより、その周囲の状態を囲む関数です。閉じられた状態は、クロージャーの呼び出し全体に残ります。
オブジェクト指向言語では、通常、クロージャはオブジェクトを通じて提供されます。ただし、一部のOO言語(C#など)は、状態を囲むオブジェクトを持たない純粋な関数型言語(lispなど)によって提供されるクロージャの定義に近い特別な機能を実装しています。
興味深いのは、C#でのラムダとクロージャの導入により、関数型プログラミングが主流の使用法に近づいていることです。
ラムダは言語の構成要素です。つまり、匿名関数の構文です。クロージャーはそれを実装するための手法です-あるいは、ファーストネームの関数、つまり名前付きか匿名か。
より正確には、クロージャとは、実行時にファーストクラスの関数がその「コード」とそのコードで使用されるすべての非ローカル変数を「クローズ」する環境のペアとして表される方法です。このようにして、それらの変数は、それらが発生する外部スコープがすでに終了している場合でも、引き続きアクセスできます。
残念ながら、ファーストクラスの値としての関数をサポートしていない、または機能を損なわれた形式でのみサポートしている言語が世の中にたくさんあります。したがって、人々はしばしば「本物」を区別するために「閉鎖」という用語を使用します。
プログラミング言語の観点から見ると、これらはまったく異なる2つのものです。
基本的に、チューリング完全言語では、抽象化、適用、削減など、非常に限られた要素のみが必要です。抽象化とアプリケーションは、ラムダ式を構築する方法を提供し、リダクションはラムダ式の意味を決定します。
Lambdaは、計算プロセスを抽象化する方法を提供します。たとえば、2つの数値の合計を計算するには、2つのパラメーターx、yを取り、x + yを返すプロセスを抽象化できます。スキームでは、次のように書くことができます
(lambda (x y) (+ x y))
パラメータの名前は変更できますが、それによって完了するタスクは変更されません。ほとんどすべてのプログラミング言語で、ラムダ式に名前を付けることができます。これは関数と呼ばれます。しかし、それほど大きな違いはありません。それらは概念的には単なる構文糖と見なすことができます。
OK、これをどのように実装できるか想像してみてください。ラムダ式をいくつかの式に適用するときはいつでも、例えば
((lambda (x y) (+ x y)) 2 3)
評価する式でパラメーターを単純に置き換えることができます。このモデルはすでに非常に強力です。ただし、このモデルではシンボルの値を変更できません。たとえば、ステータスの変更を模倣することはできません。したがって、より複雑なモデルが必要です。簡単に言うと、ラムダ式の意味を計算したいときはいつでも、シンボルと対応する値のペアを環境(またはテーブル)に入れます。次に、残りの(+ xy)は、テーブル内の対応するシンボルを検索することによって評価されます。ここで、環境で直接操作するいくつかのプリミティブを提供すると、ステータスの変化をモデル化できます!
この背景で、この機能を確認してください:
(lambda (x y) (+ x y z))
ラムダ式を評価すると、xyが新しいテーブルにバインドされることがわかっています。しかし、どのように、どこでzを検索できるでしょうか。実際には、zは自由変数と呼ばれます。zを含む外部環境が必要です。そうでなければ、式の意味はxとyをバインドするだけでは決定できません。これを明確にするために、スキームで次のように書くことができます:
((lambda (z) (lambda (x y) (+ x y z))) 1)
したがって、zは外部テーブルで1にバインドされます。2つのパラメーターを受け入れる関数も取得しますが、実際の意味は外部環境にも依存します。言い換えれば、外部環境は自由変数で閉じます。set!の助けを借りて、関数をステートフルにすることができます。つまり、それは数学の意味での関数ではありません。それが返すものは、入力だけでなくzにも依存します。
これはあなたがすでによく知っていることであり、オブジェクトのメソッドはほとんど常にオブジェクトの状態に依存しています。そのため、「クロージャは貧乏人のオブジェクト」と言う人もいます。しかし、ファーストクラスの関数が本当に好きなので、オブジェクトを貧乏人のクロージャと見なすこともできます。
私はスキームを使用してアイデアを説明しています。そのスキームは、真の閉鎖性を備えた最も初期の言語の1つです。ここにあるすべての資料は、SICPの第3章でより適切に提示されています。
要約すると、ラムダとクロージャは本当に異なる概念です。ラムダは関数です。クロージャはラムダとラムダを閉じる対応する環境のペアです。
概念は上記と同じですが、PHPのバックグラウンドを使用している場合は、PHPコードを使用してさらに説明します。
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
function($ v){return $ v> 2; }はラムダ関数の定義です。変数に格納することもできるため、再利用できます。
$max = function ($v) { return $v > 2; };
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);
次に、フィルターされた配列で許可される最大数を変更する場合はどうでしょうか。別のラムダ関数を記述するか、クロージャーを作成する必要があります(PHP 5.3)。
$max_comp = function ($max) {
return function ($v) use ($max) { return $v > $max; };
};
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));
クロージャは、それ自体の環境で評価される関数であり、関数が呼び出されたときにアクセスできる1つ以上のバインドされた変数を持っています。それらは関数型プログラミングの世界から来ており、そこでは多くのコンセプトが使われています。クロージャーはラムダ関数に似ていますが、クロージャーが定義されている外部環境からの変数と対話する機能を持っているという意味でよりスマートです。
次に、PHPクロージャーの簡単な例を示します。
$string = "Hello World!";
$closure = function() use ($string) { echo $string; };
$closure();
この質問は古く、多くの回答を得ました。
非公式の閉鎖プロジェクトであるJava 8と公式Lambdaを使用して、質問を復活させます。
Javaのコンテキストでの答え(ラムダとクロージャーを使用—違いは何ですか?):
「クロージャーは、それぞれの自由変数を値にバインドする環境と対になっているラムダ式です。Javaでは、ラムダ式はクロージャーによって実装されるため、2つの用語はコミュニティーで交換可能に使用されるようになりました。」
簡単に言えば、クロージャはスコープに関するトリックであり、ラムダは匿名関数です。ラムダによるクロージャをよりエレガントに実現でき、ラムダはより高い関数に渡されるパラメータとしてよく使用されます
Lambda式は単なる無名関数です。たとえばプレーンJavaでは、次のように記述できます。
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
public Job apply(Person person) {
Job job = new Job(person.getPersonId(), person.getJobDescription());
return job;
}
};
ここで、クラスFunctionはJavaコードでビルドされています。これで、mapPersonToJob.apply(person)
どこかで呼び出して使用できます。それはほんの一例です。構文が存在する前のラムダです。ラムダスはこのためのショートカットです。
閉鎖:
ラムダは、このスコープ外の変数にアクセスできるときにクロージャーになります。私はあなたがその魔法を言うことができると思います、それはそれが作成された環境を魔法のように包み込み、そのスコープ(外側のスコープ)の外で変数を使用することができます。
Kotlinでは、ラムダは常にそのクロージャー(外部スコープにある変数)にアクセスできます
関数が外部変数を使用して操作を実行するかどうかによって異なります。
外部変数 -関数のスコープ外で定義された変数。
ラムダ式は、操作を実行するためにパラメーター、内部変数、または定数に依存するため、ステートレスです。
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
クロージャーは、操作を実行するためにパラメーターと定数と共に外部変数(つまり、関数本体のスコープ外で定義された変数)を使用するため、状態を保持します。
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Javaはクロージャを作成するときに、変数nを関数とともに保持するため、他の関数に渡したり、どこで使用したりしても参照できます。