web-dev-qa-db-ja.com

なぜプログラムはクロージャーを使うのでしょうか?

ここでクロージャーを説明する多くの投稿を読んだ後、私はまだ重要な概念が欠けています:なぜクロージャーを書くのですか?プログラマーはどのような特定のタスクを実行するでしょうか?閉鎖によって最もよく役立つ?

Swiftのクロージャの例は、NSUrlへのアクセスとリバースジオコーダの使用です。 これはそのような例の1つです。 残念ながら、これらのコースはクロージャを提示するだけで、コードソリューションがクロージャとして記述されている理由を説明します。

現実世界のプログラミング問題の例で、脳が「ああ、私はこれのクロージャーを書くべきだ」と言う可能性があり、理論的な議論よりも有益です。このサイトで利用できる理論的な議論に事欠きません。

61
Bendrix

まず第一に、クロージャーを使用することなしには不可能なことは何もありません。いつでも特定のインターフェースを実装するオブジェクトでクロージャーを置き換えることができます。それは、簡潔さとカップリングの減少の問題です。

第二に、クロージャーはしばしば不適切に使用されることを覚えておいてください。そこでは、単純な関数参照または他の構成がより明確になるでしょう。あなたがベストプラクティスとして見るすべての例を取るべきではありません。

クロージャーが他の構成要素よりも本当に優れているのは、高次関数を使用するとき、実際に状態を通信する必要があるとき、そしてそれをワンライナーにすることができるときです。 、クロージャーの wikipediaページからのこのJavaScriptの例のように

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

ここで、thresholdは、非常に簡潔かつ自然に、定義されている場所から使用される場所まで伝達されます。その範囲は可能な限り正確に制限されます。 filterは、クライアント定義のデータをしきい値のように渡す可能性を可能にするために記述する必要はありません。この1つの小さな関数でしきい値を伝達することのみを目的として、中間構造を定義する必要はありません。完全に自己完結型です。

あなたはクロージャーなしでこれを書くことができますが、それはより多くのコードを必要とし、従うのがより難しくなります。また、JavaScriptにはかなり冗長なラムダ構文があります。たとえば、Scalaでは、関数本体全体は次のようになります。

bookList filter (_.sales >= threshold)

ただし、 ECMAScript 6 を使用できる場合は、 脂肪矢印関数 のおかげで、JavaScriptコードがはるかに単純になり、実際に配置できます単一行に。

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

独自のコードで、一時的な値をある場所から別の場所に伝えるためだけに多くのボイラープレートを生成する場所を探します。これらは、クロージャーに置き換えることを検討する優れた機会です。

33
Karl Bielefeldt

説明のために、私は クロージャーに関するこの優れたブログ投稿 からいくつかのコードを借ります。それはJavaScriptですが、JavaScriptではクロージャーが非常に重要であるため、クロージャーが使用するほとんどのブログ投稿が使用する言語です。

配列をHTMLテーブルとしてレンダリングしたいとします。あなたはこのようにすることができます:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

しかし、配列内の各要素がどのようにレンダリングされるかについては、JavaScriptのなすがままです。レンダリングを制御したい場合、これを行うことができます:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

これで、必要なレンダリングを返す関数を渡すことができます。

各テーブル行に現在の合計を表示したい場合はどうでしょうか?その合計を追跡するための変数が必要でしょうね。クロージャーを使用すると、積算合計変数をクローズするレンダラー関数を作成でき、積算合計を追跡できるレンダラーを作成できます。

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

ここで起こっている魔法は、renderIntが繰り返し呼び出されて終了しても、totalrenderInt変数へのアクセスを保持することです。

JavaScriptよりも伝統的なオブジェクト指向言語では、この合計変数を含むクラスを記述して、クロージャーを作成する代わりにそれを渡すことができます。しかし、クロージャーはそれを行うためのはるかに強力でクリーンでエレガントな方法です。

参考文献

52
Robert Harvey

closuresの目的は、単にpreserve状態にすることです。したがって、名前はclosure-itclosesover stateです。説明を簡単にするために、JavaScriptを使用します。

通常、あなたは機能を持っています

_function sayHello(){
    var txt="Hello";
    return txt;
}
_

変数のスコープはこの関数にバインドされています。したがって、実行後、変数txtはスコープ外になります。関数の実行が終了した後は、アクセスまたは使用する方法はありません。

クロージャは言語構造であり、前述のように、変数の状態を保持してスコープを延長できます。

これは、さまざまな場合に役立ちます。 1つの使用例は、 高次関数 の構築です。

数学とコンピュータサイエンスでは、高次関数(関数形、関数形、関数形)は、次のうち少なくとも1つを実行する関数です。 1

  • 1つ以上の関数を入力として受け取ります
  • 関数を出力します

単純ですが、あまりにも有用ではない例は次のとおりです。

_ makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 
_

関数makedadderを定義します。これは、1つのパラメーターを入力として受け取り、functionを返します。 outer関数function(a){}innerfunction(b){}{}。さらに、高次関数makeadderを呼び出した結果として、別の関数_add5_を(暗黙的に)定義します。 makeadder(5)は、匿名の(inner)関数を返します。この関数は1つのパラメーターを取り、outer関数とinner関数のパラメーター。

trickは、inner関数を返すときに、実際に追加すると、外部関数(a)のパラメーターのスコープが保持されます。 _add5_は、パラメータaが_5_であることを覚えています

または、少なくとも何らかの方法で役立つ例を1つ示します。

_  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));
_

別の一般的なユースケースは、いわゆる [〜#〜] iife [〜#〜] =すぐに呼び出される関数式です。 JavaScriptでは、fakeプライベートメンバー変数が非常に一般的です。これは、privatescope = closureを作成する関数を介して行われます。これは、定義が呼び出された直後に行われるためです。構造はfunction(){}()です。定義の後の括弧_()_に注意してください。これにより、オブジェクトを作成するために 明らかにするモジュールパターン を使用できます。トリックは、スコープを作成して、IIFEの実行後にこのスコープにアクセスできるオブジェクトを返すことです。

Addiの例は次のようになります。

_ var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );
_

返されたオブジェクトには関数(publicSetNameなど)への参照があり、その関数は「プライベート」変数privateVarにアクセスできます。

ただし、これらはJavaScriptのより特殊な使用例です。

プログラマーは、クロージャーによって最も効果的に実行される可能性のある特定のタスクを実行しますか?

これにはいくつかの理由があります。 機能的パラダイムに従うので、彼にとってそれは自然なことかもしれません。またはJavascriptで:言語のいくつかの癖を回避するためにクロージャーに依存することは単なる必要性です

23
Thomas Junk

クロージャには主に2つの使用例があります。

  1. Asynchrony。しばらく時間がかかるタスクを実行し、完了したら何かを実行するとします。コードが実行されるのを待つようにコードを待機させると、それ以上の実行がブロックされてプログラムが応答しなくなる可能性があります。または、タスクを非同期に呼び出して、「バックグラウンドでこの長いタスクを開始し、完了したらこのクロージャーを実行する」と言います。クロージャには、完了時に実行するコードが含まれています。

  2. Callbacks。これらは、言語とプラットフォームに応じて、「デリゲート」または「イベントハンドラ」とも呼ばれます。アイデアは、特定の明確なポイントで、コードによって渡されたクロージャーを実行するeventを実行するカスタマイズ可能なオブジェクトがあるということですそれはそれを設定します。たとえば、プログラムのUIにボタンがある場合、ユーザーがボタンをクリックしたときに実行されるコードを保持するクロージャーをそれに与えます。

クロージャーには他にもいくつかの使用法がありますが、それらは2つの主要な使用法です。

16
Mason Wheeler

他のいくつかの例:

ソート
ほとんどのソート関数は、オブジェクトのペアを比較することによって機能します。いくつかの比較手法が必要です。比較を特定の演算子に制限することは、かなり柔軟性のないソートを意味します。より良いアプローチは、比較関数をソート関数の引数として受け取ることです。ステートレス比較関数はうまく機能する場合があります(たとえば、数値または名前のリストを並べ替える)が、比較で状態が必要な場合はどうなりますか?

たとえば、都市のリストを特定の場所までの距離で並べ替えることを検討してください。醜い解決策は、その場所の座標をグローバル変数に格納することです。これにより、比較関数自体がステートレスになりますが、グローバル変数が犠牲になります。

このアプローチでは、複数のスレッドが同じ都市のリストを2つの異なる場所までの距離で同時に並べ替えることができません。ロケーションを囲むクロージャーはこの問題を解決し、グローバル変数の必要性を取り除きます。


乱数
元のRand()は引数を取りませんでした。疑似乱数ジェネレータには状態が必要です。いくつか(例えば、メルセンヌツイスター)は多くの状態を必要とします。単純ですがひどいRand()も必要な状態です。新しい乱数ジェネレータに関する数学のジャーナルの論文を読んでください。必然的にグローバル変数が表示されます。これは、技術の開発者にとっては素晴らしいことであり、呼び出し元にとってはそれほど素晴らしいことではありません。その状態を構造体にカプセル化し、構造体を乱数ジェネレータに渡すことは、グローバルデータ問題を回避する1つの方法です。これは、OO以外の多くの言語で乱数ジェネレータを再入可能にする方法です。クロージャーは呼び出し側からその状態を隠します。クロージャーは、Rand()の単純な呼び出しシーケンスとカプセル化された状態の再入可能性を提供します。

乱数には、PRNGだけではありません。ランダム性を望むほとんどの人は、それが特定の方法で分散されることを望んでいます。最初に、0から1の間、または略してU(0,1)からランダムに描かれた数値から始めます。任意のPRNGは、0から最大値までの整数を生成します;ランダムな整数を最大値で(浮動小数点として)除算します。これを実装する便利で一般的な方法は、クロージャを作成することですこれは、クロージャ(PRNG)と最大値を入力として受け取ります。これで、U(0,1)の汎用で使いやすいランダムジェネレーターができました。

U(0,1)以外にも、いくつかの分布があります。たとえば、特定の平均と標準偏差を持つ正規分布。私が実行したすべての正規分布生成アルゴリズムは、U(0,1)生成器を使用します。通常のジェネレーターを作成する便利で一般的な方法は、U(0,1)ジェネレーター、平均、および標準偏差を状態としてカプセル化するクロージャーを作成することです。これは、少なくとも概念的には、クロージャーを引数として取るクロージャーをとるクロージャーです。

13
David Hammen

クロージャーはrun()メソッドを実装するオブジェクトと同等であり、逆にオブジェクトはクロージャーでエミュレートできます。

  • クロージャーの利点は、関数を期待するところならどこでも簡単に使用できることです:別名高次関数、単純なコールバック(または戦略パターン)。アドホッククロージャーを構築するためにインターフェース/クラスを定義する必要はありません。

  • オブジェクトの利点は、複数のメソッドや異なるインターフェイスなど、より複雑な相互作用が可能になることです。

したがって、クロージャーまたはオブジェクトの使用は、ほとんどの場合スタイルの問題です。クロージャによって簡単に作成できるものの、オブジェクトを使用して実装するのが不便な例を次に示します。

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

基本的に、グローバルクロージャを介してのみアクセスされる非表示の状態をカプセル化します。オブジェクトを参照する必要はなく、3つの関数によって定義されたプロトコルのみを使用します。


一部の言語ではオブジェクトの存続期間を正確に制御できるという事実についてのsupercatの最初のコメントを信頼していますが、クロージャーについては同じことが当てはまりません。ただし、ガベージコレクションされた言語の場合、オブジェクトのライフタイムは一般に無制限であるため、呼び出されるべきではない動的なコンテキストで呼び出される可能性があるクロージャを構築することが可能です(ストリームの後にクロージャから読み取る)たとえば、閉じています)。

ただし、クロージャの実行を保護する制御変数をキャプチャすることで、このような誤用を防ぐのは非常に簡単です。より正確には、これは私が(Common LISPで)念頭に置いているものです:

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

ここでは、関数指定子functionを取り、2つのクロージャーを返します。どちらもactiveというローカル変数をキャプチャします。

  • 最初のものは、functionがtrueの場合にのみ、activeに委任します
  • 2つ目は、actionnilに設定します。別名、falseです。

(when active ...)の代わりに、当然のことながら(assert active)式を使用することもできます。これは、クロージャが呼び出されるべきでないときに呼び出された場合に例外をスローする可能性があります。また、安全でないコードは、使用方法を誤ると既に例外をスローする可能性があるため、このようなラッパーが必要になることはほとんどありません。

以下にその使用方法を示します。

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

非アクティブ化クロージャは他の関数にも与えることができることに注意してください。ここでは、ローカルのactive変数はfgの間で共有されていません。また、activeに加えて、fobj1のみを参照し、gobj2のみを参照します。

Supercatによって言及されたもう1つの点は、クロージャーがメモリーリークを引き起こす可能性があることですが、残念ながら、ガベージコレクションされた環境のほとんどすべてに当てはまります。それらが利用可能な場合、これはweak pointersによって解決できます(クロージャ自体はメモリに保持される可能性がありますが、他のリソースのガベージコレクションを妨げません)。

7
coredump

まだ言われていないことはありませんが、おそらくもっと簡単な例です。

次に、タイムアウトを使用したJavaScriptの例を示します。

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

ここで何が起きるかというと、delayedLog()が呼び出されると、タイムアウトを設定した直後に戻り、タイムアウトがバックグラウンドで刻々と下がり続けます。

ただし、タイムアウトが発生してfire()関数を呼び出すと、最初はdelayedLog()に渡されたmessageがクロージャーを介してfire()で引き続き使用できるため、コンソールに表示されます。あなたは好きなだけdelayedLog()を呼び出すことができ、毎回異なるメッセージと遅延を使用することができ、それは正しいことをします。

しかし、JavaScriptにクロージャーがないとしましょう。

1つの方法は、setTimeout()ブロッキングを「スリープ」関数のように作成することです。そのため、delayedLog()のスコープは、タイムアウトがなくなるまで消えません。しかし、すべてをブロックすることは、とても良いことではありません。

もう1つの方法は、_message変数を、delayedLog()のスコープがなくなった後にアクセスできる他のスコープに配置することです。

グローバル変数、または少なくとも「より広いスコープ」変数を使用できますが、どのメッセージがどのタイムアウトで発生するかを追跡する方法を理解する必要があります。ただし、必要に応じて遅延を設定できるため、FIFOキューだけではありません。つまり、「先入れ先出し」などの場合があります。そのため、時限関数を必要な変数に結びつける他の手段。

タイマーとメッセージを「グループ化」するタイムアウトオブジェクトをインスタンス化できます。オブジェクトのコンテキストは、多かれ少なかれ、固執するスコープです。次に、オブジェクトのコンテキストでタイマーを実行するため、適切なメッセージにアクセスできます。しかし、参照がないとガベージコレクションが行われるため、そのオブジェクトを格納する必要があります(クロージャーがないと、暗黙的な参照もありません)。そして、タイムアウトが発生したら、オブジェクトを削除する必要があります。そうしないと、オブジェクトがそのまま残ります。したがって、タイムアウトオブジェクトのある種のリストが必要であり、削除する「使用済み」オブジェクトがないか定期的にチェックします。そうしないと、オブジェクトがリストに追加され、リストから削除されます...

だから...ええ、これは鈍くなっています。

ありがたいことに、より広いスコープを使用したり、特定の変数を保持するためだけにオブジェクトを作成したりする必要はありません。 JavaScriptにはクロージャーがあるので、必要なスコープをすでに持っています。必要なときにmessage変数にアクセスできるスコープ。そのため、上記のようにdelayedLog()を書くことで回避できます。

6
Flambino

[〜#〜] php [〜#〜] は、実際の例を別の言語で表示するのに役立ちます。

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

したがって、基本的には/ api/users [〜#〜] uri [〜#〜] に対して実行される関数を登録しています。これは実際にはミドルウェア機能であり、スタックに格納されます。他の関数はその周りにラップされます。 Node.js / Express.js とほぼ同じです。

依存性注入 コンテナーは、関数が呼び出されたときに関数内で(use句を介して)使用できます。なんらかのルートアクションクラスを作成することは可能ですが、このコードはシンプルで、高速で、保守が容易であることがわかります。

3
Cerad