web-dev-qa-db-ja.com

'クロージャ'と 'ラムダ'の違いは何ですか?

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

そして今、私たちはここにいるので、それらは通常の関数とどう違うのでしょうか?

749
sker

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

閉鎖 任意の関数です 閉まります の 環境 それが定義されたところ。これは、パラメータリストにない変数にアクセスできることを意味します。例:

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

funcはエラーにならないため、これはエラーの原因となります。 閉じる anotherfunc - h内の環境は未定義です。 funcはグローバル環境のみを閉じます。これは動作します:

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

なぜならここでは、funcanotherfunc、そしてPython 2.3以降(あるいはこのような数字)で定義されているからです。 ほとんど クロージャーが正しくなった(突然変異はまだうまくいかない)、これはそれがそれを意味する 閉まります anotherfuncの環境であり、その中の変数にアクセスできます。 Python 3.1以降では、 nonlocalキーワード を使用した場合でも突然変異は機能します。

もう一つの重要なポイント - funcは、anotherfuncで評価されなくなっても、anotherfuncの環境を閉じこめていきます。このコードも動作します:

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

print anotherfunc(10)()

これは10を印刷します。

お気づきのとおり、これは何の関係もありません。 ラムダs - それらは2つの異なる(関連しているが)概念です。

656
Claudiu

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

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;
           };
 }
172
Mark Cidade

すべてのクロージャがラムダではなく、すべてのラムダがクロージャでもありません。どちらも関数ですが、必ずしも慣れ親しんだ方法ではありません。

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

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

オブジェクト指向言語では、クロージャは通常オブジェクトを通して提供されます。ただし、OO言語(C#など)の中には、純粋に 関数型言語 (LISPなど)で提供されるクロージャの定義に近い特殊な機能を実装するものがあります。状態。

興味深いのは、C#にLambdasとClosuresを導入したことで、関数型プログラミングが主流の用法に近づいたことです。

52
Michael Brown

それはこれと同じくらい簡単です:ラムダは言語の構成要素、すなわち単に無名関数のための構文です。クロージャはそれを実装するためのテクニックです - あるいはそのことについて言えば、名前付きまたは無名の任意の一流関数です。

もっと正確に言うと、クロージャーとは、 ファーストクラス関数 が実行時に、そのコードで使用されているすべての非ローカル変数に対する「コード」と環境の「クロージング」のペアとして表現される方法です。このように、それらが由来する外側のスコープが既に存在していたとしても、それらの変数はまだアクセス可能です。

残念ながら、第一級の値としての機能をサポートしていない、またはそれらを不自由な形式でしかサポートしていない多くの言語があります。だから人々はしばしば "本物"を区別するために "クロージャ"という用語を使用します。

14

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

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

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

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

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

さて、今これがどのように実装されることができるか想像してください。ラムダ式をいくつかの式に適用するたびに、.

((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にも依存します。

これはあなたがすでによく知っていることです。オブジェクトのメソッドはほとんどの場合オブジェクトの状態に依存しています。だからこそ、私たちはファーストクラスの機能が好きなので、オブジェクトを貧乏人のクロージャと見なすこともできます。

私はschemeを使ってアイデアを説明しています。そのschemeは本物のクロージャーを持つ最も初期の言語のひとつです。ここにあるすべての資料は、SICPの第3章に掲載されています。

まとめると、ラムダとクロージャは本当に異なる概念です。ラムダは関数です。クロージャとは、ラムダとそれを閉じる環境のペアです。

12
Wei Qiu

この質問は古く、多くの答えを得ました。非公式クロージャープロジェクトであるJava 8とOfficial Lambdaを使用して、問題を復活させます。

Javaの文脈での答えは( Lambdas and closures - 違いは何ですか? を介して):

「クロージャーは、それぞれの自由変数を値にバインドする環境と対になったラムダ式です。Javaでは、ラムダ式はクロージャーによって実装されるため、コミュニティでは2つの用語が同じ意味で使用されるようになりました。」

7
philippe lhardy

概念は上記と同じですが、あなたがPHP backgroundから来ている場合、これは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();

この記事でうまく説明されています。

7
Developer

簡単に言えば、クロージャはスコープに関するトリックです、ラムダは無名関数です。ラムダによるクロージャをよりエレガントに実現することができ、ラムダはより高い関数に渡されるパラメータとしてよく使われます。

5
feilengcui008

ラムダ式は匿名関数です。たとえば、普通の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)を呼び出して使用することができます。それはほんの一例です。それのための構文がある前にラムダと言います。これは、ラムダのショートカットです。

閉鎖:

このスコープ外の変数にアクセスできる場合、Lambdaはクロージャになります。私はあなたがその魔法を言うことができると思います、それは魔法のようにそれが作られた環境を包み込みそしてその範囲外の変数を使うことができます.

kotlinでは、ラムダは常にそのクロージャ(その外側のスコープにある変数)にアクセスできます。

1
j2emanue

関数が外部変数を使用しているかどうかによって異なります。

外部変数 - 関数の範囲外で定義された変数。

  • ラムダ式は、操作を実行するためにパラメータ、内部変数、または定数に依存するため、ステートレスです。

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • クロージャホールド状態これは、操作を実行するためにパラメータと定数と共に外部変数(すなわち、関数本体の範囲外で定義された変数)を使うからです。

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Javaがクロージャを作成するとき、それは他の関数に渡されたとき、またはどこでも使われるときに参照されることができるように関数と変数nを保ちます。

0
DIPANSHU GOYAL