質問

誰か説明できますか?私はそれらの背後にある基本的な概念を理解していますが、それらは同じ意味で使用されていることがよくあり、混乱します。

そしてここに来たので、通常の機能とどう違うのですか?

役に立ちましたか?

解決

A lambda は単なる匿名関数です-名前なしで定義された関数です。 Schemeなどの一部の言語では、名前付き関数と同等です。実際、関数定義は、ラムダを変数に内部的にバインドするように書き直されています。 Pythonのような他の言語では、いくつかの(やや不必要な)違いがありますが、それ以外は同じように動作します。

A closure は、それが定義された environment 閉じる関数です。これは、パラメータリストにない変数にアクセスできることを意味します。例:

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以降(またはこのような数字)では almost クロージャは正しい(突然変異はまだ機能しない)。つまり、 anotherfunc の環境をクローズし、その中の変数にアクセスできることを意味します。 Python 3.1以降では、 nonlocal キーワード

別の重要なポイント- anotherfunc で評価されなくなった場合でも、 anotherfunc の環境では func が引き続き閉じられます。このコードも機能します:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

これは10を印刷します。

これは、ご存知のとおり、 lambda とは関係ありません-それらは2つの異なる(関連している)概念です。

他のヒント

このStackOverflowの質問への回答でも、ラムダとクロージャーについては多くの混乱があります。特定のプログラミング言語や他の無知なプログラマーの練習からクロージャーについて学んだランダムなプログラマーに尋ねる代わりに、 source (すべてが始まった)への旅をしてください。そして、ラムダとクロージャーは、最初の電子コンピューターが存在する前の30年代にアロンゾ教会が発明したラムダ計算に由来するため、これが私が話しているソースです。

Lambda Calculusは、世界で最も単純なプログラミング言語です。あなたがそれでできる唯一のこと:►

  • アプリケーション:1つの式を別の式に適用し、 fx と表示します。
    function call と考えてください。ここで、 f は関数と x が唯一のパラメーターです)
  • 抽象化:式に出現するシンボルをバインドして、このシンボルが「スロット」、値の入力を待機している空白ボックス、「変数」であることをマークします。そのまま。ギリシャ文字のλ (ラムダ)を追加し、次にシンボル名(例: x )を追加し、次にドットを追加します。式の前。次に、式を function に変換して、1つの parameter を期待します。
    たとえば、λ x.x + 2 は式 x + 2 で、この式のシンボル x バインドされた変数–であることを示します。パラメータとして指定した値で置き換えることができます。
    この方法で定義された関数は anonymous –であることに注意してください。名前がないため、まだ参照できませんが、次のように、待機しているパラメーターを指定することで、すぐに呼び出すことができます(アプリケーションを覚えていますか?) code>(λ x.x + 2)7 。次に、式(この場合はリテラル値) 7 は、適用されたラムダの部分式 x + 2 x に置き換えられるため、 7 + 2 を取得し、一般的な算術ルールにより 9 に削減します。

だから私たちは謎の一つを解決しました:
lambda は、上記の例λ x.x + 2 匿名関数です。


異なるプログラミング言語では、機能の抽象化(ラムダ)の構文が異なる場合があります。たとえば、JavaScriptでは次のようになります。

function(x) { return x+2; }

これを次のようなパラメータにすぐに適用できます:

(function(x) { return x+2; })(7)

またはこの匿名関数(ラムダ)を変数に保存できます:

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  )


さて、今度はもう1つの謎、つまり closure を解決するときです。 それを行うために、ラムダ式の symbols variables )について話しましょう。

前述したように、ラムダ抽象化は、サブ表現内のシンボルをバインドすることで、置換可能なパラメータになります。このようなシンボルは bound と呼ばれます。しかし、式に他の記号がある場合はどうでしょうか?例:λ x.x / y + 2 。この式では、シンボル x は、その前にあるラムダ抽象化λ x。によってバインドされています。しかし、他のシンボル y はバインドされていません– 無料です。私たちはそれが何であり、どこから来たのかわからないので、それが何を意味するのかがわからないため、その式を評価することはできません y の意味がわかるまで。

実際には、他の2つのシンボル 2 および + 。これら2つのシンボルに精通しているだけで、通常はコンピューターがそれらを認識していないことを忘れてしまいます。ライブラリまたは言語自体。

free シンボルは、式の外側の「周囲のコンテキスト」で定義され、「環境」と呼ばれます。環境は、この表現が一部であるより大きな表現かもしれません(Qui-Gon Jinnが言ったように:「より大きな魚が常にいる」;))、またはいくつかのライブラリー、または言語自体(として)プリミティブ)。

これにより、ラムダ式を2つのカテゴリに分類できます。

  • 閉じた式:これらの式で発生するすべてのシンボルは、ラムダ抽象化によってバインドされます。言い換えれば、それらは自己完結型です。周囲のコンテキストを評価する必要はありません。これらは combinators とも呼ばれます。
  • OPEN式:これらの式の一部のシンボルは、バインド–つまり、それらのシンボルの一部は free であり、外部情報を必要とするため、これらのシンボルの定義を提供するまで評価できません。

open ラムダ式を閉じるには、 environment を指定します。これにより、これらのすべてのシンボルをいくつかの値(数値、文字列、匿名関数、別名ラムダ、…)。

次に、閉鎖の部分があります:
ラムダ式クロージャは、外部コンテキスト(環境)で定義されたこの特定のシンボルのセットであり、この無料のシンボルに値を与えます表現、それらを非フリーにする。 open ラムダ式に変わりますが、ラムダ式にはまだ「未定義」の一部が含まれています。無料のシンボルは、閉じたものになり、無料のシンボルはもうありません。

たとえば、次のラムダ式がある場合:λ xx / y + 2 、シンボル x はバインドされ、シンボル y は無料であるため、式は open であり、 y の意味を言うまで評価できません(および + でも同じです)および 2 も無料です)。ただし、次のような環境もあるとします。

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

この環境は、すべての" undefined"の定義を提供します。 (無料)ラムダ式のシンボル( y + 2 )、およびいくつかの追加シンボル( q w )。定義する必要があるシンボルは、環境のこのサブセットです。

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

そしてこれはまさにラムダ式の閉鎖です:>

つまり、開いているラムダ式を閉じます。そもそも名前が closure の由来であり、このため、このスレッドで非常に多くの人々の答えが正しくありません:P


なぜ彼らは間違っているのですか?なぜそんなに多くの人がクロージャーをメモリ内のデータ構造、または使用する言語の機能であると言うのか、なぜクロージャーとラムダを混同するのですか? :P

さて、Sun / Oracle、Microsoft、Googleなどの企業市場は非難するべきです。なぜなら、彼らは彼らの言語(Java、C#、Goなど)でこれらのコンストラクトを呼んだからです。彼らはしばしば「クロージャ」と呼んでいます。単なるラムダとなるはずのもの。または、「クロージャ」と呼ばれます。レキシカルスコープの実装に使用された特定の手法、つまり、関数がその定義時に外側のスコープで定義された変数にアクセスできるという事実。彼らはしばしば、関数が「囲む」と言います。これらの変数、つまり、それらをいくつかのデータ構造にキャプチャして保存しますf

ほとんどの人が関数について考えるとき、彼らは名前付き関数について考えます:

function foo() { return "This string is returned from the 'foo' function"; }

これらはもちろん名前で呼ばれます:

foo(); //returns the string above

lambda式を使用すると、匿名関数を使用できます:

 @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)}); 

A closure は名前付き関数または匿名関数の場合がありますが、「閉じる」ときにそのように知られています。関数が定義されているスコープ内の変数、つまり、クロージャーは、クロージャー自体で使用される外部変数を持つ環境を引き続き参照します。名前付きクロージャーは次のとおりです。

@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;
           };
 }

すべてのクロージャーがラムダであるわけではなく、すべてのラムダがクロージャーであるわけでもありません。どちらも関数ですが、必ずしも私たちが知っている方法ではありません。

ラムダは基本的に、関数を宣言する標準的な方法ではなく、インラインで定義される関数です。ラムダはオブジェクトとして頻繁に渡すことができます。

クロージャーは、その体の外部のフィールドを参照することで周囲の状態を囲む関数です。閉じられた状態は、クロージャーの呼び出し全体にわたって残ります。

オブジェクト指向言語では、クロージャは通常オブジェクトを通じて提供されます。ただし、一部のオブジェクト指向言語(C#など)は、純粋にによって提供されるクロージャーの定義により近い特別な機能を実装しています。状態を囲むオブジェクトを持たない関数型言語(Lispなど)。

興味深いのは、C#でLambdasとClosuresを導入すると、関数型プログラミングが主流の使用法により近くなることです。

これはこれと同じくらい簡単です:ラムダは言語構成体です。つまり、匿名関数の単なる構文です。クロージャは、それを実装するための手法です。つまり、名前付きまたは匿名のファーストクラス関数です。

より正確には、クロージャーとは、ファーストクラス関数が実行時にどのように表されるかです。 、「コード」のペアとしておよび環境「閉じる」そのコードで使用されるすべての非ローカル変数に対して。このようにして、それらの変数は、それらが発生した外側のスコープが既に終了している場合でもアクセス可能です。

残念ながら、ファーストクラスの値としての関数をサポートしていないか、それらを不自由な形でしかサポートしていない言語がたくさんあります。したがって、人々は「クロージャ」という用語をよく使用します。 「本物」を区別するため。

プログラミング言語の観点から見ると、これらはまったく異なる2つのものです。

基本的にチューリング完全言語の場合、必要な要素は非常に限られています。抽象化、適用、および削減。抽象化とアプリケーションは、lamdba式を構築する方法を提供し、縮小はラムダ式の意味を決定します。

Lambdaは、計算プロセスを抽象化する方法を提供します。 たとえば、2つの数値の合計を計算するには、2つのパラメーターx、yを取り、x + yを返すプロセスを抽象化できます。スキームでは、次のように記述できます

(lambda (x y) (+ x y))

パラメーターの名前を変更できますが、完了するタスクは変更されません。 ほとんどすべてのプログラミング言語では、ラムダ式に名前を付けることができます。これは名前付き関数です。ただし、大きな違いはありません。概念的には単なる構文シュガーと見なすことができます。

OK、これをどのように実装できるか想像してみてください。ラムダ式をいくつかの式に適用するときはいつでも、例えば

((lambda (x y) (+ x y)) 2 3)

パラメータを評価する式に単純に置き換えることができます。このモデルはすでに非常に強力です。 しかし、このモデルでは、シンボルの値を変更することはできません。ステータスの変化を真似することはできません。したがって、より複雑なモデルが必要です。 短くするために、ラムダ式の意味を計算したいときはいつでも、シンボルと対応する値のペアを環境(またはテーブル)に入れます。次に、残りの(+ x y)は、テーブル内の対応するシンボルを検索して評価されます。 環境を直接操作するためのプリミティブを提供する場合、ステータスの変化をモデル化できます!

この背景で、この機能を確認してください:

(lambda (x y) (+ x y z))

ラムダ式を評価すると、x yが新しいテーブルにバインドされることがわかります。しかし、どのように、どこで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){$ 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とOfficial Lambdaにより、問題が復活しました。

Javaコンテキストでの回答(ラムダとクロージャー&#8212経由) ;違いは何ですか?):

  

"クロージャーは、各自由変数を値にバインドする環境とペアになったラムダ式です。 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)を呼び出して使用できます。それはほんの一例です。それは構文が存在する前のラムダです。ラムダはこのための近道です。

閉鎖:

a Lambdaは、このスコープ外の変数にアクセスできる場合、クロージャーになります。私はあなたがその魔法を言うことができると思う、それは魔法のようにそれが作成された環境を包み込み、そのスコープの外側の変数を使用することができる(外側のスコープ。 >

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を保持するため、他の関数に渡されたり、どこで使用されても参照できるようになります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top