39

クロージャのサンプルを見たことがあります- 「クロージャ」とは何ですか?

クロージャをいつ使用するかの簡単な例を誰かが提供できますか?

具体的には、閉鎖が理にかなっているシナリオですか?

言語がクロージャーをサポートしていないと仮定しましょう。それでも同様のことをどのように達成できるでしょうか。

Not to offend anyone, please post code samples in a language like c#, python, javascript, ruby etc.
I am sorry, I do not understand functional languages yet.

4

8 に答える 8

33

クロージャは素晴らしいツールです。それらをいつ使用するのですか?好きなときにいつでも... すでに述べたように、別の方法はクラスを作成することです。たとえば、C# 2.0 より前のバージョンでは、パラメーター化されたスレッドを作成するのは非常に困難でした。C# 2.0 では、次のように「ParameterizedThreadStart」さえも必要ありません。

string name = // blah
int value = // blah
new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short

それを名前と値を持つクラスの作成と比較してください

または、同様にリストを検索します (今回はラムダを使用):

Person person = list.Find(x=>x.Age > minAge && x.Region == region);

繰り返しますが、別の方法は、2 つのプロパティとメソッドを持つクラスを作成することです。

internal sealed class PersonFinder
{
    public PersonFinder(int minAge, string region)
    {
        this.minAge = minAge;
        this.region = region;
    }
    private readonly int minAge;
    private readonly string region;
    public bool IsMatch(Person person)
    {
        return person.Age > minAge && person.Region == region;
    }
}
...
Person person = list.Find(new PersonFinder(minAge,region).IsMatch);

これは、コンパイラがボンネットの下でそれを行う方法とかなり似ています (実際には、プライベートな読み取り専用ではなく、パブリックな読み取り/書き込みフィールドを使用します)。

C# キャプチャに関する最大の注意点は、スコープを監視することです。例えば:

        for(int i = 0 ; i < 10 ; i++) {
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(i);
            });
        }

変数i がそれぞれに使用されているため、これは期待どおりに出力されない場合があります。繰り返しの任意の組み合わせを見ることができます - 10 10 も。C# でキャプチャされた変数のスコープを慎重に設定する必要があります。

        for(int i = 0 ; i < 10 ; i++) {
            int j = i;
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(j);
            });
        }

ここでは、各 j が個別にキャプチャされます (つまり、コンパイラによって生成された異なるクラス インスタンス)。

Jon Skeet は、C# と Java のクロージャについて説明している優れたブログ エントリをここで公開しています。詳細については、彼の著書C# in Depthを参照してください。これには、それらに関する章全体があります。

于 2008-11-02T08:34:44.617 に答える
22

「いつも」という以前の回答に同意します。関数型言語またはラムダとクロージャーが一般的な言語でプログラミングする場合、気付かないうちにそれらを使用しています。「関数のシナリオは何ですか?」と尋ねるようなものです。または「ループのシナリオは何ですか?」これは、元の質問を愚かに聞こえるようにするためではなく、特定のシナリオに関して定義していない言語の構成要素があることを指摘するためです。常にそれらを使用するだけです。すべての場合、それは第二の性質です。

これはどういうわけか次のことを連想させます。

由緒あるマスターQc Naは、彼の学生、アントンと一緒に歩いていました。マスターに議論を促したいと思って、アントンは「マスター、オブジェクトはとても良いものだと聞きましたが、本当ですか?」と言いました。Qc Na は同情するように彼の生徒を見て、「愚かな生徒 - オブジェクトは単に貧乏人の閉鎖です。」 と答えました。

懲らしめられたアントンは、主人から離れて独房に戻り、閉鎖を研究することに専念しました。彼は「Lambda: The Ultimate...」シリーズのすべての論文とそのいとこを注意深く読み、クロージャーベースのオブジェクト システムを備えた小さな Scheme インタープリターを実装しました。彼は多くのことを学び、主人に進捗状況を報告することを楽しみにしていました。

次の Qc Na との散歩で、アントンは主人に感銘を与えようと、「マスター、私はこの問題を熱心に研究しましたが、オブジェクトは本当に貧しい人の閉鎖であることがわかりました」と言いました。Qc Na は、Anton をスティックで叩いて、「いつ学習しますか? 閉鎖は貧乏人の目的です」と答えました。その瞬間、アントンは悟りを開いた。

( http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html )

于 2008-11-02T08:03:28.563 に答える
14

クロージャーを使用する最も単純な例は、カリー化と呼ばれるものです。f()基本的に、 2 つの引数aとを指定して呼び出すとb、それらを加算する関数があると仮定しましょう。したがって、Python では次のようになります。

def f(a, b):
    return a + b

しかし、議論のために、一度に 1 つの引数だけを呼び出したいとしましょうf()。したがって、 の代わりにf(2, 3)、 が必要ですf(2)(3)。これは次のように実行できます。

def f(a):
    def g(b): # Function-within-a-function
        return a + b # The value of a is present in the scope of g()
    return g # f() returns a one-argument function g()

を呼び出すとf(2)、新しい関数g();が得られます。この新しい関数は のスコープからの変数を持っているため、それらの変数を閉じるf()と言われているため、クロージャという用語が使用されています。を呼び出すと、変数( の定義によってバインドされている) が によってアクセスされ、戻り値が返されます。g(3)afg()2 + 3 => 5

これは、いくつかのシナリオで役立ちます。たとえば、多数の引数を受け入れる関数があるが、そのうちのいくつかしか役に立たない場合、次のような汎用関数を書くことができます。

def many_arguments(a, b, c, d, e, f, g, h, i):
    return # SOMETHING

def curry(function, **curry_args):
    # call is a closure which closes over the environment of curry.
    def call(*call_args):
        # Call the function with both the curry args and the call args, returning
        # the result.
        return function(*call_args, **curry_args)
    # Return the closure.
    return call

useful_function = curry(many_arguments, a=1, b=2, c=3, d=4, e=5, f=6)

useful_functionは、9 つ​​ではなく 3 つの引数しか必要としない関数になりました。繰り返す必要がなくなり、一般的な解決策も作成されました。別の多引数関数を作成すれば、curryツールを再び使用できます。

于 2008-11-02T08:29:26.640 に答える
11

通常、クロージャーがない場合は、クラスを定義して、クロージャーの環境と同等のものを持ち、それを渡す必要があります。

たとえば、Lisp のような言語では、次のように、定義済みの量をその引数に追加する関数を返す関数を定義できます (クローズド オーバー環境を使用)。

(defun make-adder (how-much)
  (lambda (x)
    (+ x how-much)))

次のように使用します。

cl-user(2): (make-adder 5)
#<Interpreted Closure (:internal make-adder) @ #x10009ef272>
cl-user(3): (funcall * 3)     ; calls the function you just made with the argument '3'.
8

クロージャーのない言語では、次のようにします。

public class Adder {
  private int howMuch;

  public Adder(int h) {
    howMuch = h;
  }

  public int doAdd(int x) {
    return x + howMuch;
  }
}

そして、次のように使用します。

Adder addFive = new Adder(5);
int addedFive = addFive.doAdd(3);
// addedFive is now 8.

クロージャーは暗黙的にその環境を一緒に運びます。実行部分 (ラムダ) 内からその環境をシームレスに参照します。クロージャーがなければ、その環境を明示的にする必要があります。

これは、いつクロージャーを使用するかを説明するはずです: all time . クラスがインスタンス化されて、計算の別の部分からの状態を保持し、それを別の場所に適用するほとんどのインスタンスは、それらをサポートする言語のクロージャーにエレガントに置き換えられます。

クロージャを使用してオブジェクト システムを実装できます。

于 2008-11-02T07:39:45.367 に答える
3

これは、Python の標準ライブラリである inspect.py の例です。それは現在読んでいます

def strseq(object, convert, join=joinseq):
    """Recursively walk a sequence, stringifying each element."""
    if type(object) in (list, tuple):
        return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object))
    else:
        return convert(object)

これは、convert 関数と join 関数をパラメーターとして持ち、リストとタプルを再帰的に走査します。再帰は、最初のパラメーターが関数である map() を使用して実装されます。このコードは、Python でのクロージャのサポートよりも前のものであるため、convert と join を再帰呼び出しに渡すために、2 つの追加のデフォルト引数が必要です。クロージャーを使用すると、これは次のようになります

def strseq(object, convert, join=joinseq):
    """Recursively walk a sequence, stringifying each element."""
    if type(object) in (list, tuple):
        return join(map(lambda o: strseq(o, convert, join), object))
    else:
        return convert(object)

OO 言語では、オブジェクトを使用して状態とバインドされたメソッドを渡すことができるため、通常、クロージャーはあまり使用しません。Python にクロージャがなかったとき、Python はオブジェクトでクロージャをエミュレートするのに対し、Lisp はクロージャでオブジェクトをエミュレートすると人々は言いました。IDLE (ClassBrowser.py) の例:

class ClassBrowser: # shortened
    def close(self, event=None):
        self.top.destroy()
        self.node.destroy()
    def init(self, flist):
        top.bind("<Escape>", self.close)

ここで、self.close は、Escape が押されたときに呼び出されるパラメーターなしのコールバックです。ただし、close 実装にはパラメーターが必要です。つまり、self、次に self.top、self.node です。Python にメソッドがバインドされていない場合は、次のように記述できます。

class ClassBrowser:
    def close(self, event=None):
        self.top.destroy()
        self.node.destroy()
    def init(self, flist):
        top.bind("<Escape>", lambda:self.close())

ここで、ラムダはパラメーターからではなく、コンテキストから「自己」を取得します。

于 2008-11-02T09:02:53.483 に答える
1

haskellにはもっと多くの用途があると言われていますが、私はjavascriptでクロージャを使用することを楽しんだだけであり、javascriptではその意味があまりわかりません。私の最初の本能は、クロージャを機能させるために実装がどのように混乱しなければならないかについて、「ああ、二度とではない」と叫ぶことでした。クロージャがどのように実装されているか(とにかくjavascriptで)を読んだ後、今はそれほど悪くはないようで、少なくとも私にとっては実装はややエレガントに見えます。

しかし、そのことから、「閉鎖」は実際には概念を説明するのに最適な言葉ではないことに気づきました。「フライングスコープ」と名付けたほうがいいと思います。

于 2011-03-24T18:25:44.383 に答える
1

Lua と Python では、「コーディングするだけ」のときは非常に自然なことです。パラメーターではない何かを参照した瞬間にクロージャーを作成しているためです。(そのため、これらのほとんどは例として非常に退屈です。)

具体的なケースとして、ステップが (undo(), redo()) クロージャーのペアである元に戻す/やり直しシステムを想像してください。これを行うためのより面倒な方法は、(a) やり直し不可のクラスに普遍的に無意味な引数を持つ特別なメソッドを持たせるか、(b) UnReDoOperation を何度もサブクラス化することです。

もう 1 つの具体的な例は、無限リストです。一般化されたコンテナーを操作する代わりに、次の要素を取得する関数をフロブします。(これはイテレータの力の一部です。) この場合、ほんの少しの状態 (次の整数、すべての非負整数のリストなど) を保持するか、オブジェクト内の位置への参照を保持できます。実際のコンテナ。いずれにせよ、それは自分自身の外にあるものを参照する関数です。(無限リストの場合、状態変数はクロージャー変数でなければなりません。そうしないと、すべての呼び出しでクリーンになるためです)

于 2008-11-02T07:58:52.677 に答える