なぜプログラムはクロージャーを使用するのですか?


58

ここでクロージャーを説明する多くの投稿を読んだ後、私はまだ重要な概念がありませんなぜクロージャーを書くのですか? プログラマーが実行する特定のタスクのうち、クロージャーが最も役立つと思われるものは何ですか?

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

私の脳を「あぁ、これのためにクロージャを書くべきだ」と言うかもしれない現実世界のプログラミング問題の例は、理論的な議論よりも有益でしょう。このサイトで利用可能な理論的議論の不足はありません。


7
「ここでクロージャの説明を最近レビューしました」-リンクがありませんか?
ダンピチェルマン

2
非同期タスクに関する説明を、関連する回答の下のコメントに入れる必要があります。元の質問の一部ではないため、回答者に編集内容が通知されることはありません。
ロバートハーヴェイ

5
ちょっとした注意:クロージャの重要なポイントは、(動的スコープとは対照的に)字句スコープです。字句スコープのないクロージャは、まったく役に立ちません。クロージャは、語彙クロージャとも呼ばれます。
ホフマン

1
クロージャを使用すると、関数インスタンス化できます。
user253751

1
関数型プログラミングに関する優れた本を読んでください。おそらくSICP
バジル・スタリンケビッチ

回答:


33

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

第二に、単純な関数参照やその他の構造がより明確になる場合、クロージャはしばしば不適切に使用されることに留意してください。ベストプラクティスとして見られるすべての例を取り上げるべきではありません。

クロージャーが実際に他のコンストラクトよりも優れているのは、高次関数を使用する場合、実際に状態を伝達する必要がある場合、クロージャーに関するウィキペディアページのこのJavaScriptの例のように、1ライナーにすることができます:

// 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コードもはるかに単純になり、実際には1行に配置できます。

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

独自のコードで、一時的な値をある場所から別の場所に伝えるためだけに、大量の定型文を生成する場所を探します。これらは、クロージャに置き換えることを検討する絶好の機会です。


クロージャーはどの程度正確に結合を低減しますか?
sbichenko

クロージャを使用しないと、データを通信できるようにするために、特定のインターフェイスやユーザーデータ引数など、bestSellingBooksコードとコードの両方に制限を課す必要がfilterありthresholdます。これにより、2つの機能が再利用性の低い方法で結び付けられます。
カールビーレフェルト

51

説明のために、クロージャーに関するこの優れたブログ投稿からいくつかのコードを借ります。これは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 total変数renderIntが繰り返し呼び出されて終了しても、変数へのアクセスを保持することです。

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

参考文献


10
一般的に、「ファーストクラス関数==メソッドが1つだけのオブジェクト」、「クロージャー==メソッドと状態が1つだけのオブジェクト」、「オブジェクト==クロージャーのバンドル」と言うことができます。
ヨルグWミッターグ

いいね。もう1つ、そしてこれは教訓かもしれませんが、Javascript オブジェクト指向であり、合計変数を含むオブジェクトを作成して渡すことができます。クロージャーは、はるかに強力で、クリーンで、エレガントなままであり、はるかに慣用的なJavascriptを作成しますが、その記述方法は、Javascriptがオブジェクト指向の方法でこれを実行できないことを意味する場合があります。
KRyan

2
実のところ、本当につまらないものになるために、クロージャはそもそもJavaScriptをオブジェクト指向にするものです!OOはデータの抽象化に関するものであり、クロージャーはJavaScriptでデータの抽象化を実行する方法です。
ヨルグWミッターグ

@JörgWMittag:1つのメソッドだけでオブジェクトとしてオブジェクトを見ることができるように、同じ変数を閉じるクロージャーのコレクションとしてオブジェクトを見ることができます:オブジェクトのメンバー変数は、オブジェクトのコンストラクターのローカル変数であり、オブジェクトのメソッドですコンストラクターのスコープ内で定義され、後で呼び出せるようになったクロージャーです。コンストラクター関数は、呼び出しに使用されるメソッド名に従って各クロージャーでディスパッチできる高次関数(オブジェクト)を返します。
ジョルジオ

@Giorgio:確かに、例えば、それはSchemeでの典型的な実装方法であり、実際にはオブジェクトのJavaScriptでの実装方法でもあります(結局、Schemeとの密接な関係を考えると、Brendan Eichはもともと、 Scheme方言とNetscape Navigator内の埋め込みSchemeインタープリターを実装し、後になって「C ++のようなオブジェクトを持つ」言語を作成するように命じられ、その後、マーケティング要件を満たすために最小限の変更を行いました。
ヨルグWミッターグ

22

目的closuresは単に状態を保持することです。したがって、名前closure-それは状態を閉じます。説明を簡単にするために、Javascriptを使用します。

通常、機能があります

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

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

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

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

数学およびコンピュータサイエンスでは、高次関数は、(また、機能的形態、機能的またはファンクタ)以下の少なくとも一つを行う機能である:1

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

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

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

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

関数を定義しますmakedadder。これは、入力として1つのパラメーターを取り、関数を返します。あり、外側の関数function(a){}内側の function(b){}{}あなたは(暗黙的に)別の関数を定義.Further add5高次の目的球を呼び出した結果としてはmakeaddermakeadder(5)匿名(内部)関数を返します。この関数は1つのパラメーターを取り、外部関数のパラメーターと内部関数のパラメーターの合計を返します。

The は、実際の追加を行う内部関数を返す間、外部関数(a)のパラメーターのスコープが保持されることです。パラメータがであることをadd5 思い出します。a5

または、少なくとも何らかの形で有用な例を示します:

  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では、プライベートメンバー変数を偽造することは非常に一般的です。これは、定義を呼び出した直後にプライベートスコープ= を作成する関数を介して行われclosureます。構造はfunction(){}()です。()定義の後の括弧に注意してください。これにより、モジュールパターン明らかにしながらオブジェクト作成に使用することができます。秘Theは、スコープを作成し、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の場合:クロージャーに頼って言語のいくつかの癖を回避する必要はありません。


「クロージャの目的は、単に状態を保持することです。したがって、クロージャという名前は-状態の上で閉じます。」:それは、実際に言語に依存します。クロージャーは外部名/変数を閉じます。これらは状態(変更可能なメモリ位置)を示す場合がありますが、値(不変)を示す場合もあります。Haskellのクロージャーは状態を保持しません。クロージャーが作成された時点およびコンテキストで知られている情報を保持します。
ジョルジオ

1
»現時点で、およびクロージャが作成されたコンテキストで知られている情報を保存します«変更可能かどうかに関係なく、それは状態 -おそらく不変状態です。
トーマスジャンク

16

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

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

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

クロージャーには他にもいくつかの用途がありますが、これらは主に2つの用途です。


23
最初の例もコールバックであるため、基本的にはコールバックです。
ロバートハーベイ

2
@RobertHarvey:技術的には本当ですが、それらは異なるメンタルモデルです。たとえば、(一般的に言えば)イベントハンドラーが複数回呼び出されることを期待していますが、非同期継続は1回しか呼び出されません。しかし、はい、技術的にはクロージャーで行うことはすべてコールバックです。(式ツリーに変換している場合を除き、それはまったく別の問題です。);)
メイソンウィーラー

4
@RobertHarvey:クロージャーをコールバックとして表示すると、クロージャーを効果的に使用するのを実際に妨げる心のフレームになります。クロージャーは、ステロイドのコールバックです。
gnasher729

13
@MasonWheelerこのように言えば、間違っているように聞こえます。コールバックはクロージャーとは関係ありません。もちろん、クロージャ内の関数をコールバックするか、コールバックとしてクロージャを呼び出すことができます。しかし、コールバックは必ずしも必要ではありません。コールバックは単なる関数呼び出しです。イベントハンドラー自体はクロージャーではありません。デリゲートはクロージャーである必要はありません:それは主に関数ポインターのC#の方法です。もちろん、これを使用してクロージャを構築できます。あなたの説明は不正確です。ポイントは、状態閉じて、それを利用することです。
トーマスジャンク

5
ここでJS固有の意味合いがあり、誤解を招いているかもしれませんが、この答えはまったく間違っているように思えます。非同期またはコールバックは、「呼び出し可能」なものをすべて使用できます。どちらもクロージャーは必要ありません-通常の関数で問題ありません。一方、関数型プログラミングで定義されているか、Pythonで使用されているクロージャーは、トーマスが言うように便利です。関数が何度も呼び出されて終了する場合でも値。
ジョナサンハートリー

13

他のいくつかの例:

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

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

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


乱数
オリジナルrand()は引数を取りませんでした。擬似乱数ジェネレータには状態が必要です。いくつか(たとえば、Mersenne Twister)は多くの状態を必要とします。シンプルだがひどいrand()必要な状態でさえ。新しい乱数ジェネレーターに関する数学ジャーナルの論文を読むと、必然的にグローバル変数が表示されます。これは、この手法の開発者にとっては良いことですが、呼び出し元にとってはそれほど良いことではありません。その状態を構造体にカプセル化し、構造体を乱数ジェネレーターに渡すことは、グローバルデータの問題を回避する1つの方法です。これは、乱数ジェネレータを再入可能にするために多くの非OO言語で使用されるアプローチです。クロージャーは、その状態を呼び出し元から隠します。クロージャは、rand()カプセル化された状態の単純な呼び出しシーケンスと再入可能性を提供します。

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

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


7

クロージャは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)))))

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

  • 最初の代表者にfunctionのみ、active真であります
  • 2番目のものは、別名に設定さactionれます。nilfalse

代わりに(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変数はとの間fで共有されませんg。また、に加えてactivefのみを参照しobj1gのみを参照しobj2ます。

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


1
一般的に実装されているクロージャーの欠点は、メソッドのローカル変数の1つを使用するクロージャーが外部に公開されると、クロージャーを定義するメソッドがその変数の読み取りと書き込みを制御できなくなることです。ほとんどの言語では、エフェメラルクロージャを定義し、それをメソッドに渡し、それを受け取ったメソッドが戻ったときに存在しなくなることを保証する便利な方法はありません。また、マルチスレッド言語では予期しないスレッドコンテキストでクロージャが実行されないことを保証します。
-supercat

@supercat:Objective-CとSwiftをご覧ください。
gnasher729

1
@supercatステートフルオブジェクトにも同じ欠点はありませんか?
アンドレスF.

@AndresF .:無効化をサポートするラッパーにステートフルオブジェクトをカプセル化できます。コードがプライベートに保持さList<T>れた(仮想クラス)TemporaryMutableListWrapper<T>をカプセル化し、それを外部コードに公開する場合、ラッパーを無効にすると、外部コードはを操作する方法を持たなくなりますList<T>。予想される目的を果たした後、無効化を許可するようにクロージャーを設計できますが、それはほとんど便利ではありません。特定のパターンを便利にするためにクロージャーが存在し、それらを保護するために必要な努力はそれを無効にします。
-supercat

1
@coredump:C#では、2つのクロージャーに共通の変数がある場合、1つのクロージャーによって共有変数に加えられた変更を他のクロージャーが確認する必要があるため、同じコンパイラー生成オブジェクトが両方に対応します。共有を回避するには、各クロージャが、共有変数を保持する共有オブジェクトへの参照と同様に、非共有変数を保持する独自のオブジェクトである必要があります。不可能ではありませんが、ほとんどの変数アクセスへの逆参照の追加レベルが追加され、すべてが遅くなります。
supercat

6

まだ言われていないことは何もありませんが、もっと簡単な例かもしれません。

タイムアウトを使用する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()関数を呼び出すと、コンソールmessageは元々に渡されたものを表示しdelayedLog()ますfire()。これは、クロージャを介してまだ利用可能であるためです。必要なだけ呼び出すことができdelayedLog()、毎回異なるメッセージで遅延させることができ、それは正しいことをします。

しかし、JavaScriptにクロージャーがないことを想像してみましょう。

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

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

グローバル変数、または少なくとも「より広いスコープ」変数を使用できますが、どのメッセージがどのタイムアウトになったかを追跡する方法を理解する必要があります。ただし、任意の遅延を設定できるため、単なるFIFOの順次キューにすることはできません。そのため、「先入れ先出し」などの可能性があります。そのため、時限関数を必要な変数に関連付けるには、他の手段が必要になります。

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

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

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


私はそれを閉鎖と呼ぶ問題を抱えています。たぶん私はそれを偶然に閉じたと思います。messageの関数スコープに囲まれているfireため、以降の呼び出しで参照されます。しかし、それは言うまでもなく偶然です。技術的には閉鎖です。とにかく+1;)
トーマスジャンク

@ThomasJunk私はかなり従うかどうかわかりません。「偶発的閉鎖」はどのように見えるでしょうか?makeadder上記の例を見てみましたが、私の目にはほとんど同じように見えます。2つではなく1つの引数を取る「カリー化」関数を返します。同じ手段を使用して、引数がゼロの関数を作成します。私はそれを返さず、setTimeout代わりに渡します。
フランビーノ

»私はそれを返さないだけです«おそらく、それが私にとって違いを生むポイントです。私の「懸念」を明確に表現することはできません;)技術的には、あなたは100%正しいです。を参照するとクロージャmessagefire生成されます。そして、それが呼び出されるとsetTimeout、保存された状態を利用します。
トーマスジャンク

1
@ThomasJunk匂いが少し違うかもしれません しかし、とにかく、それを「偶然」とは呼びません
意図

3

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とほぼ同じです。

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


-1

クロージャは、ファーストクラスのデータとして扱うことができる変数を含む任意のコードの断片です。

ささいな例は古き良きqsortです。これはデータをソートする関数です。2つのオブジェクトを比較する関数へのポインターを与える必要があります。したがって、関数を作成する必要があります。その関数はパラメーター化する必要がある場合があります。つまり、静的変数を指定する必要があります。つまり、スレッドセーフではありません。DSにいます。そのため、関数ポインターの代わりにクロージャーを取る代替手段を作成します。パラメーターはクロージャーの一部になるため、パラメーター化の問題を即座に解決できます。オブジェクトを並べ替え関数を呼び出すコードと直接比較する方法を記述するため、コードが読みやすくなります。

大量のボイラープレートコードに加えて、適応する必要がある1つの小さなが不可欠なコードを必要とするアクションを実行したい状況がたくさんあります。定型コードを回避するには、クロージャーパラメーターを受け取り、その周りのすべての定型コードを実行する関数を1回作成します。次に、この関数を呼び出して、クロージャーとして適合させるコードを渡すことができます。コードを記述するための非常にコンパクトで読みやすい方法。

多くの異なる状況で、重要なコードを実行する必要がある機能があります。これは、コードの重複またはゆがんだコードのいずれかを生成するために使用されたため、重要なコードは一度しか存在しません。自明:クロージャを変数に割り当て、必要な場所で最も明白な方法で呼び出します。

マルチスレッド:iOS / MacOS Xには、「バックグラウンドスレッドでこのクロージャーを実行する」、「...メインスレッドで」、「...メインスレッドで、10秒後」などの機能があります。マルチスレッドを簡単にします。

非同期呼び出し:それはOPが見たものです。インターネットにアクセスする呼び出し、または時間がかかる可能性のある呼び出し(GPS座標の読み取りなど)は、結果を待つことができないものです。したがって、バックグラウンドで処理を行う関数があり、クロージャーを渡して、それらが終了したときに何をするかを伝えます。

それは小さなスタートです。コンパクトで読みやすく信頼性の高い効率的なコードを生成するという点でクロージャーが革新的な5つの状況。


-4

クロージャは、使用されるメソッドを記述する簡単な方法です。別のメソッドを宣言して記述する手間が省けます。メソッドが1回だけ使用され、メソッド定義が短い場合に役立ちます。関数の名前、戻り値の型、またはアクセス修飾子を指定する必要がないため、利点は入力が減ることです。また、コードを読むとき、メソッドの定義を他の場所で探す必要はありません。

上記は、Dan AvidarによるLambda Expressionsの概要です。

これにより、代替手段(クロージャーとメソッド)およびそれぞれの利点が明確になるため、クロージャーの使用が明確になりました。

次のコードは、セットアップ中に1回だけ使用されます。viewDidLoadの下に適切に記述することで、他の場所で検索する手間が省け、コードのサイズが短縮されます。

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

さらに、プログラムの他の部分をブロックせずに完了する非同期プロセスを提供し、クロージャーは後続の関数呼び出しで再利用するために値を保持します。

別の閉鎖; これは値をキャプチャします...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)

4
残念ながら、これはクロージャーとラムダを混同します。ラムダは、a)非常に簡潔で、b)変数を含む特定のメソッドコンテキスト内で依存する場合に定義されると、通常はるかに便利なので、多くの場合、クロージャを使用します。ただし、実際のクロージャはラムダの概念とは関係ありません。ラムダは基本的に、一流の関数を適切に定義し、後で使用するために渡す機能です。
ネイサンタギー

この答えを投票している間、これを読んでください...いつ投票するべきですか?ひどくずさんな、労力のかからない投稿、または明らかに危険なほど間違った回答に遭遇した場合は、必ずダウン票を使用してください。私の答えは、クロージャーを使用する理由のいくつかを欠いているかもしれませんが、それはひどくずさんでもなく、危険なほど間違っていません。関数型プログラミングとラムダ表記法を中心としたクロージャに関する多くの論文と議論があります。私の答えは、この関数型プログラミングの説明がクロージャを理解するのに役立ったということです。それと永続的な価値。
ベンドリックス

1
答えはおそらく危険ではありませんが、確かに「明らかに[…]間違っています」。高度に補完的であり、したがって頻繁に一緒に使用される概念に対して、区別の明確さが不可欠​​な場合に、間違った用語を使用します。これは、アドレスを指定しないままにしておくと、これを読んでいるプログラマーに設計上の問題を引き起こす可能性があります。(そして、私が知る限り、コード例には単にクロージャーが含まれておらず、ラムダ関数のみが含まれているため、クロージャーが役立つ理由を説明する助けにはなりません。)
Nathan Tuggy

Nathan、そのコード例はSwiftのクロージャーです。あなたが私を疑うなら、あなたは他の人に尋ねることができます。それで、それが閉鎖であるならば、あなたはそれを投票するでしょうか?
ベンドリックス

1
Apple は文書で閉鎖が意味するものをかなり明確に説明しています。「クロージャーは、コードに渡して使用できる自己完結型の機能ブロックです。Swiftのクロージャーは、CおよびObjective-Cのブロック、および他のプログラミング言語のラムダに似ています。」実装と用語を理解すると、混乱を招く可能性があります
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.