8

このサイトやラムダとクロージャーに関する他のサイトの多くの投稿を 1 時間かけて読みました。それらが何であるか、つまりどのように機能するかは理解していると思いますが、なぜそれらが存在するのかはわかりません。私が目にする多くの例は、それらの「力」に漠然と言及していますが、それらの各ケースで、説明されていることを達成するためのはるかに簡単な方法を考えることができます. おそらくこれは、(理解しやすくするために) 例が意図的に過度に単純化されているためか、あるいは私が密集しているからかもしれません。しかし、私が本当に望んでいるのは、クロージャーまたはラムダで達成できないことの明確な例を見ることですなしで達成します。すべてのプログラミング パラダイムは最終的には同じ機械語命令に要約され、ある言語で実行できることは別の言語でも実行できるため、おそらくこれはよくある質問です。したがって、私が本当に求めているのは、クロージャーを使用しない場合よりもエレガントなクロージャーを使用して何かを実行した例です (これは、私がまだ見たどの例にも当てはまらないようです)。

これが私が話していることです。

「閉鎖」とは何ですか?に対する主な回答 、Scheme の例:

(define (make-counter)
  (let ((count 0))
    (lambda ()
      (set! count (+ count 1))
      count)))

(define x (make-counter))

(x) returns 1

(x) returns 2

...etc...

私は、Scheme についてはよくわかりませんが、何が起こっているかはわかります。しかし、これはより簡単で、同じことを達成できるのではないでしょうか? 擬似コード:

function incrementCount(var counter) {
    counter++;
    return counter;
}

counter = 1;
counter = incrementCount(counter);
counter = incrementCount(counter);

ラムダに関する別の例:ラムダ (関数) とは?

与えられた JavaScript の例:

var adder = function (x) {
    return function (y) {
        return x + y;
    };
};
add5 = adder(5);
add5(1) == 6

繰り返しますが、なぜそのようにするのですか?なぜ言わないのですか:

function adder(x, y) {
    return x + y;
}
adder(5, 1);

私が見たすべての例は、基本的な関数で簡単に実行できることを行うには、非常に複雑な方法のように思えます。これらの例は、私が見ることができる驚くほどクールな「力」を示しているわけではありません。誰かが私を啓発してください、私は何かを見逃しているに違いありません。ありがとう。

4

4 に答える 4

5

ラムダ計算は実際には非常に単純で、それ自体ではそれほど役に立ちません。ただし、ラムダ計算の意味と、機能設計パターンを使用したアプリケーション制御の戦略を学び始めると、より優れた、より強力なプログラマーになることができます。

関数は値でもあるという前提があり、これにより、多くの概念を抽象化するのに非常に強力になります。あなたの2つの例は、クロージャーとカリー化が実装される最も簡単な方法を示しています。これらは些細な例です。関数型スタイルを使用してプログラミングを開始すると、それらのパターンは、複雑さを抽象化するための非常に強力な方法として何度も何度も出てきます。

関心の分離

関数型プログラミングは、高階関数を使用して抽象化を開始するときに役立ちます。典型的な例は、オブジェクトのコレクションに対して何かをしたい場合です。

したがって、命令型プログラムでは、for ループを使用します。

result = Array(length);
for(i = 0; i < length; i++)
{
   result[i] = dosomething(input[i]);
}

dosomethingコードのブロックで、関数が for ループの真ん中にあることに注意してください。配列の各要素に対して行っていることを、実際の for ループ制御構造から切り離すことはできません。

ただし、関数型スタイルを使用することで、ループの制御構造はmap高次関数を使用して抽象化され、ループと関数適用の 2 つの概念が明確に分離されます。これはスキームの同等のコードです。

(define result (map dosomething collection))

map配列の各要素をトラバースしているという事実を処理します。dosomething配列の各要素に対して操作を実行しているという事実を処理します。これについての推論がより明確になり、コードの変更がより簡単になります。forここで、コード内のすべてのループがこの構造に置き換えられ、それによってコードが何行節約されるかを想像してください。

安いコンストラクタ

クロージャとカリー化の概念への対処。オブジェクトと関数の間には同等性があります。基本的に、関数はオブジェクトのように見え、動作するように作成できます。Javascript はこの事実を大いに利用します。オブジェクトでできることはすべて、関数でもできます。実際、オブジェクトと関数の区別をなくすことで、効果的に頭の中が整頓され、問題に対する 1 つの考え方が得られます。

ここでは clojure コードを使用しています。

これは単純な加算器の例の clojure コードです。加算器の例では、数値 5 を使用して を呼び出しaddnて、数値に 5 を加算する関数を取得しています。一般的な使用例は次のとおりです。

(defn addn [n] (fn [m] (+ m n))
(def add5 (addn 5))

(map add5 [1 2 3 4 5 6])
;; => [6 7 8 9 10 11]

download-and-save-toURL とデータベースを受け取り、その URL をデータベース テーブルに保存し、成功すると true を返す関数があるとし+ます。

(defn download-url-to [db]
  (fn [url] (download-and-save-to url db)))

(def download-url-mydb (download-url-to mydb))
(map download-url-mydb [url1 url2 url3 url4 url5])
;; => [true true false true true]

実行していることは大きく異なりますが、構造は同じであることに注意してください。Java または C++ を使用してこの問題にどのようにアプローチするかを考えてみてください。クラス定義、ファクトリ メソッド、クラスの抽象化、継承などに関連するコードはさらに多くなります。同じ効果を得るには、さらに多くの儀式を行う必要があります。

心を開く

アイデアを非常に簡潔に表現できる方法があるため、関数型プログラミングを使用すると、より冗長な言語では表現するのが非常に難しい多くの概念を理解できます。選択したツールによって、特定の問題の調査が容易になったり困難になったりします。

clojure - (http://www.4clojure.com/) と lighttable - (http://www.lighttable.com/, http://www.kickstarter.com/projects/ibdknox/light-table )を強くお勧めします。始めます。私の個人的な経験からすると、clojure コミュニティで概念を学び、探求した 1 年間は、Java、C++、および python を合わせて約 10 年間学習したことに似ていました。


ああ、私はこのすべてのことを学ぶのがどれだけ楽しいか言いましたか? モナド、コンビネーター、プロパゲーター、継続...これらの恐ろしく見えるアカデミックな概念はすべて、それらを理解するための適切なツールがあれば、実際には手の届くところにあります。

于 2012-12-02T11:28:31.253 に答える
4

すでにすべての基礎をカバーしているため、この質問に答えるのはかなり困難です: 例、説明、説明を既に見てきました。主な機能。

それにもかかわらず、私が個人的にラムダ式を使用する最も一般的な使用例の 1 つと思われる1 つの例を挙げようと思います。基本的に命令型言語である C# を使用しますが、ラムダ式が組み込み機能として含まれているため、例は C# になります。

値を必要とする関数があるとします。ただし、この値の計算にはコストがかかるため、要件は次のとおりです。

  • 関数が値を必要としない場合は、まったく評価されません。
  • 関数が値を複数回必要とする場合、一度だけ評価されます。

ラムダ式を使用すると、取得した変数を使用して計算値を「キャッシュ」できるため、これが非常に簡単になります。

bool computed = false;
MyType value = null;

var getValue = () => {
    if (!computed)
    {
        value = computeValue();    // this is the expensive operation
        computed = true;
    }
    return value;
};

これで、任意の関数に渡すことができます。getValueこの関数が何を行うかに関係なく (つまり、何回呼び出しても)、computeValue()一度しか呼び出されないことがわかります。

ラムダなしで同じことを行うには、このラムダのクロージャーと同等のクラスを作成し、ある種のインターフェイスを実装する必要があります。次に例を示します。

sealed class Closure : IGetMyTypeValue
{
    bool Computed = false;
    MyType Value = null;

    public Closure() { }

    public MyType GetValue()   // implements IGetMyTypeValue.GetValue()
    {
        if (!Computed)
        {
            Value = ComputeValue();    // this is the expensive operation
            Computed = true;
        }
        return Value;
    }

    private MyType ComputeValue()
    {
        // expensive code goes here
    }
}

[...]

var closure = new Closure();
// pass closure to a method that expects an IGetMyTypeValue

この大規模なクラスを作成する必要があることの欠点は次のとおりです。

  • そのような高価な計算ごとに新しいクラスを作成する必要があります。これは非常に反復的な作業です。ラムダ式を使用すると、コンパイラはそれを自動化できます。
  • 操作の種類ごとに個別のインターフェイスを作成する必要があります。
于 2012-12-02T10:39:14.733 に答える
2

JavaScript に精通しているかどうかはわかりませんが、クロージャーとラムダは、オブジェクト指向の動作をシミュレートするための基本的な機能です。

次のコードを検討してください。

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });
};

var myButtonClickCounter = new MyClass();

JavaScript にはクラスプライベート メンバーなどはありませんが、それらのように機能する構造を作成できます。これが何をしているのかを説明したコードです:

// I define a class constructor that takes a string as an input parameter
var MyClass = function(buttonId) {
  // I define a private member called click
  var clickCounter = 0;

  // I get the reference to the button with the given id.
  var myButton = document.getElementById(buttonId);
  // I attach to it a function that will be triggered when you click on it.
  // The addEventListener method takes two arguments as input:
  //  - the name of the event
  //  - the function to be called when the event is triggered
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });
};

// I create an instance of MyClass for a button called myButton
var myButtonClickCounter = new MyClass('myButton');

この場合、addEventListenerメソッドに渡される関数はラムダ関数です。はい、いつでも他の方法で定義できますが、それは単なる実装の詳細です。この場合、次MyClassのように、ラムダ関数の代わりに定義内に別の変数を作成できます。

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var clickHandler = function() {
      clickCounter += 1;
  };

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    clickHandler
  );
};

JavaScript について言えることは、チェーン ( )の上の関数で定義されているためclickCounter、そのラムダ関数のクロージャースコープにありますが、 だけでなく、そのスコープにも存在するためです。MyClassclickHandlerclickCounter

このクラスが 4 つの異なるボタンへのリスナーを作成する必要があると仮定します。それぞれのボタンに対してハンドラーを作成する必要があり、1 つがトリガーされるたびに、すべての変数がそのクロージャー スコープに存在します。そして、8 つの変数参照 (4 つのハンドラー + 4 つのカウンター) を持つことは、単に 4 つのカウンターを持つことよりも悪いことです。

このクロージャーを持つことのもう 1 つの利点は、MyClassclickCounterプライベート メンバーであることです。これは、本体で変数として定義されているため、外部からは見えないためです。代わりに、clickHandler(ラムダ関数形式であっても)は内部で定義されているため、 クロージャーMyClassを介して参照できます。

編集: 何とかclickCounter便利にするために、カウンターが特定の値に達したかどうかを通知するパブリックメソッドが必要だと想像してください。次のように書くことができます (定義の一番下):

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });

  // This method takes a value x as input, and returns
  // a boolean value, that tells you whether the button
  // has been clicked more than the given x.
  this.hasReached = function(x) {
    return (clickCounter >= x);
  };
};

// Somewhere else in your code...
var myButtonClickCounter = new MyClass('myButton');
...
if( myButtonClickCounter.hasReached(4) )
{
  // Do some great stuff
}

プライベート変数の値を単に返すか変更する従来のゲッターとセッターを使用することもできますが、それは必要なものによって異なります。この特定の実装では、private 変数を直接公開したくありません (メンバーの操作方法を知っているのがクラスだけである場合は、コードの保守が容易になるため、これを強くお勧めします)。

注:これはクロージャーの使用法を示すための単なる例であることを覚えておいてください。JavaScript でクラスを定義するより効率的な方法があります!

于 2012-12-02T11:30:08.230 に答える
0

他の投稿された回答のいくつかは、クロージャーを使用してプライベート変数を作成することを参照していますが、John Resig の高度な JavaScript チュートリアルのこの例は、はるかに明確になっていると思います。おそらく、他の誰かがそれから恩恵を受けるでしょう。

ソース: http://ejohn.org/apps/learn/#54

function Ninja(){
  var slices = 0;

  this.getSlices = function(){
    return slices;
  };
  this.slice = function(){
    slices++;
  };
}

var ninja = new Ninja();
ninja.slice();

console.log(ninja.getSlices()); // 1!
console.log(slices); // undefined!
于 2013-03-29T01:37:02.877 に答える