web-dev-qa-db-ja.com

クロージャー:なぜそんなに便利なのですか?

[〜#〜] oo [〜#〜] 開発者として、その値を確認するのが難しいかもしれません。彼らはどんな付加価値を与えますか? OO世界に収まるか?

55
koen

クロージャーはあなたに余分な力を与えません。

あなたが彼らなしで達成できる彼らと一緒に達成できることなら何でも。

しかし、それらはコードをより明確で読みやすくするために非常に便利です。そして、ご存じのとおり、クリーンで読み取り可能な短いコードは、デバッグがより簡単で、バグの数が少ないコードです。

短いJava可能な使用例:

    button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
            System.out.println("Pressed");
        }
    });

(Javaにクロージャがあった場合)と置き換えられます:

button.addActionListener( { System.out.println("Pressed"); } );
29

クラスの一般化として見ることができます。

クラスにはいくつかの状態があります。メソッドが使用できるいくつかのメンバー変数があります。

クロージャーは、関数にローカル状態へのアクセスを与えるためのより便利な方法です。

関数で使用するローカル変数を認識するクラスを作成する代わりに、その場で関数を定義するだけで、現在表示されているすべての変数に暗黙的にアクセスできます。

伝統的なOOP=言語でメンバーメソッドを定義する場合、そのクロージャは「このクラスで見えるすべてのメンバー」です。

「適切な」クロージャをサポートする言語はこれを単純に一般化するため、関数のクロージャは「ここに表示されるすべての変数」です。 "here"がクラスの場合、従来のクラスメソッドがあります。

"here"が別の関数の内部にある場合、関数型プログラマーがクロージャーと見なしているものを持っています。これで、関数は親関数に表示されていたすべてにアクセスできます。

したがって、これは単なる一般化であり、「関数はクラス内でのみ定義できる」というばかげた制限を取り除きますが、「関数は、変数が宣言された時点で可視の変数をすべて見ることができる」という考えを維持します。

67
jalf

私にとって、クロージャーの最大の利点は、タスクを開始し、タスクを実行したままにし、タスクが完了したときに何が起こるかを指定するコードを書いているときです。一般的に、タスクの最後に実行されるコードは、最初に利用可能なデータにアクセスする必要があり、クロージャーはこれを簡単にします。

たとえば、JavaScriptの一般的な用途は、HTTPリクエストを開始することです。それを始めている人はおそらく、応答が到着したときに何が起こるかを制御したいと思うでしょう。だからあなたはこのようなことをするでしょう:

function sendRequest() {
  var requestID = "123";
  Ext.Ajax.request({
    url:'/myUrl'
    success: function(response) {
      alert("Request " + requestID + " returned");
    }
  });
}

JavaScriptのクロージャーのため、「requestID」変数は成功関数内にキャプチャされます。これは、リクエスト関数とレスポンス関数を同じ場所に記述し、それらの間で変数を共有する方法を示しています。クロージャーがないと、requestIDを引数として渡すか、requestIDと関数を含むオブジェクトを作成する必要があります。

28
JW.

私見それは、コードのブロックをキャプチャできるようになるということになりますおよび後である時点で参照され、必要に応じて///実行されるコンテキスト。

それらは大したことではないように見えるかもしれません、そしてクロージャーは間違いなくあなたが日常的に物事を成し遂げるために必要なものではありません-しかし、それらはできますコードコードは、書き込み/管理がはるかにシンプルでクリーンになります。

[編集-上記のコメントに基づくコードサンプル]

Java:

List<Integer> numbers = ...;
List<Integer> positives = new LinkedList<Integer>();
for (Integer number : integers) {
    if (number >= 0) {
        positives.add(number);
    }
}

Scala(型推論やワイルドカードなど、他のいくつかの優れた点をスキップして、クロージャーの効果を比較するだけです):

val numbers:List[Int] = ...
val positives:List[Int] = numbers.filter(i:Int => i >= 0)
5
Martin

それは残念です、人々はもはやeduでSmalltalkを学びません。そこで、制御構造、コールバック、コレクション列挙、例外処理などにクロージャが使用されます。素敵な小さな例として、ワーカーキューアクションハンドラスレッド(Smalltalk)を次に示します。

|actionQueue|

actionQueue := SharedQueue new.
[
    [
        |a|

        a := actionQueue next.
        a value.
    ] loop
] fork.

actionQueue add: [ Stdout show: 1000 factorial ].

また、Smalltalkを読むことができない人にとっては、JavaScript構文でも同じです。

var actionQueue;

actionQueue = new SharedQueue;
function () {
    for (;;) {
        var a;

        a = actionQueue.next();
        a();
    };
}.fork();

actionQueue.add( function () { Stdout.show( 1000.factorial()); });

(まあ、ご覧のとおり:構文はコードの読み取りに役立ちます)

編集:actionQueueがブロックの内側からどのように参照されるかに注意してください。これは、分岐したスレッドブロックでも機能します。それがクロージャをとても使いやすくするものです。

4
blabla999

ここにいくつかの興味深い記事があります:

3
Jesper

クロージャーは、OOの世界にかなりよく適合します。

例として、C#3.0を考えてみます。これにはクロージャーと他の多くの機能的側面がありますが、それでも非常にオブジェクト指向の言語です。

私の経験では、C#の機能的側面tendはクラスメンバーの実装内にとどまり、オブジェクトのパブリックAPIの一部としてではありません露出アップ。

そのため、クロージャの使用は、それ以外の場合はオブジェクト指向コードの実装の詳細になる傾向があります。

ユニットテストの1つ( Moq に対する)からのこのコードスニペットが示すように、私は常にそれらを使用しています。

var typeName = configuration.GetType().AssemblyQualifiedName;

var activationServiceMock = new Mock<ActivationService>();
activationServiceMock.Setup(s => s.CreateInstance<SerializableConfigurationSection>(typeName)).Returns(configuration).Verifiable();

C#のクロージャー機能がなければ、入力値(typeName)をMockの期待値の一部として指定するのはかなり困難でした。

3
Mark Seemann

終了クエリについて:「(クロージャ)はOO世界に適合しますか?」

多くの言語では、クロージャはファーストクラスの関数とともに、OOプログラムを考案するための基盤を提供します。Javascript、Lua、Perlが思い浮かびます。

別の「伝統的な」OO私がよく知っている言語であるJavaでは、2つの主要な違いがあります。

  1. 関数はnotファーストクラスコンストラクト
  2. Javaでは、OO(クロージャーが内部で使用されるかどうか)の実装の詳細)は、Javascript、Lua、Perlのように開発者に直接公開されません。

したがって、Javaなどの「伝統的なOO」言語では、クロージャーを追加することは、大部分が構文上の砂糖です。

0
George Jempty