機能しない設定でのクロージャーの実装の問題


18

プログラミング言語では、クロージャは人気があり、しばしば望まれる機能です。ウィキペディアによると(強調鉱山):

コンピューターサイエンスでは、クロージャー(...)は、その関数の非ローカル変数の参照環境と一緒の関数です。クロージャを使用すると、関数は直接のレキシカルスコープ外の変数にアクセスできます。

そのため、クロージャーは本質的に(匿名?)関数値であり、それ自身のスコープ外の変数を使用できます。私の経験では、これは、定義ポイントでスコープ内にある変数にアクセスできることを意味します。

ただし、実際には、少なくとも関数型プログラミング以外では、概念は異なるようです。異なる言語は異なるセマンティクスを実装しており、意見の対立さえあるようです。多くのプログラマーは、クロージャーが何であるかを知らないようで、それらを匿名関数にすぎないと見なしています。

また、クロージャを実装するときに大きなハードルが存在するようです。最も注目すべきは、Java 7にはそれらが含まれているはずでしたが、この機能は将来のリリースにプッシュバックされたことです。

閉鎖がなぜ理解するのが難しいのか、理解するのが難しいのはなぜですか?これはあまりにも広範で曖昧な質問なので、これらの相互接続された質問にさらに焦点を当てましょう。

  • 一般的なセマンティック形式(小さなステップ、大きなステップなど)でクロージャを表現する際に問題はありますか?
  • 既存の型システムは閉鎖に適しておらず、簡単に拡張できませんか?
  • クロージャを従来のスタックベースのプロシージャ変換に合わせるのは問題ですか?

質問は主に手続き型、オブジェクト指向、およびスクリプト言語全般に関連していることに注意してください。私の知る限り、関数型言語には問題はありません。


良い質問。クロージャーはScalaで実装されており、Martin OderskyはJava 1.5コンパイラーを書いたので、なぜJava 7にないのかは明確ではありません。C#にはあります。(後でより良い答えを書いてみます。)
デイブクラーク

4
LispやMLのような不純な関数型言語は、クロージャーにうまく対応します。そのため、それらが問題になるという本質的な意味上の理由はあり得ません。
ジル 'SO-悪であるのをやめる'

クロージャーの小さなステップのセマンティクスがどのように見えるか想像するのに苦労してきたので、アイテムを含めました。クロージャ自体は問題ではないかもしれませんが、クロージャを念頭に置いて設計されていない言語にクロージャを含めることは困難です。
ラファエル

1
見てくださいpdfs.semanticscholar.org/73a2/...を - Luaの著者はそれを非常に巧妙な方法で作られたと同様にクロージャを実現するための一般的な問題を議論
Bulat

回答:


10

私はあなたを導くことができるFunarg問題のWikipediaのページ?少なくともこれは、コンパイラーの人々がクロージャー実装の問題を参照するのに使用した方法です。

そのため、クロージャーは本質的に(匿名?)関数値であり、それ自身のスコープ外の変数を使用できます。私の経験では、これは、定義ポイントでスコープ内にある変数にアクセスできることを意味します。

この定義は理にかなっていますが、従来のランタイムスタックベースの言語でファーストクラスの関数を実装する問題を説明するのに役立ちません。実装の問題になると、ファーストクラスの機能は大きく2つのクラスに分類できます。

  • 関数が返された後、関数内のローカル変数は使用されません。
  • ローカル変数は、関数が戻った後に使用できます。

最初のケース(下向きfunargs)は実装がそれほど難しくなく、Algol、C、Pascalなどの古い手続き型言語でも見られます。Cはネストされた関数を許可しませんが、AlgolとPascalは必要なブックキーピングを実行して、内部関数が外部関数のスタック変数を参照できるようにするため、Cが問題を回避します。

一方、2番目のケース(上向きfunargs)では、アクティベーションレコードをスタック外のヒープに保存する必要があります。これは、言語ランタイムにガベージコレクターが含まれていない限り、メモリリソースのリークが非常に簡単であることを意味します。今日ではほとんどすべてがガベージコレクションされていますが、それを必要とすることは依然として重要な設計上の決定事項であり、さらに以前のことです。


Javaの特定の例について、正しく覚えていれば、主な問題は実際にクロージャーを実装できないことでしたが、既存の機能(匿名内部クラスなど)と重複しない方法でクロージャーを言語に導入する方法でした。既存の機能と衝突しませんでした(チェック例外など-解決するのが難しい問題ではなく、ほとんどの人が最初は考えていない問題です)。

また、thisselfsuperなどの「魔法の」変数で何をするかを決定したり、breakreturnなどの既存の制御フロー演算子とやり取りする方法を決定するなど、ファーストクラス関数を実装するのが簡単になる他のことを考えることもできます(非ローカルリターンを許可するかどうか)。しかし、最終的には、最近のファーストクラス関数の人気は、歴史的な理由または初期の重要な設計上の決定のために、ほとんどの関数を持たない言語がほとんどそうでないことを示しているようです。


1
上向きと下向きのケースを区別する言語を知っていますか?.NET言語では、下方向のみの関数を受け取ると予想されるジェネリックメソッドは、ジェネリック型の構造体とbyref(C#では「refパラメーター」)のような構造体を受け取るデリゲートを受け取ることができます。呼び出し側が構造内のすべての対象変数をカプセル化した場合、デリゲートは完全に静的であり、ヒープ割り当ての必要性を回避できます。コンパイラーは、このような構成体に対する優れた構文ヘルプを提供しませんが、フレームワークはそれらをサポートできます。
supercat 14年

2
@supercat:Rustには、内部関数がヒープを使用する必要がある場合にコンパイル時に強制できる複数のクロージャータイプがあります。ただし、これは、これらの余分な型をすべて気にすることなく、実装がヒープ割り当てを回避しようとできないことを意味するものではありません。コンパイラーは、関数の有効期間を推測しようとするか、ランタイムチェックを使用して、厳密に必要な場合にのみ変数をヒープに遅延保存します(詳細については、Luaの論文の「lexical scope」セクションをご覧ください)
hugomg

5

C#でクロージャーがどのように実装されるかを見ることができます。C#コンパイラが実行する変換の規模により、クロージャーを実装する方法が非常に多くの作業であることが明らかになります。クロージャーを実装する簡単な方法があるかもしれませんが、C#コンパイラチームはこれを認識していると思います。

次の擬似C#を検討してください(C#固有のものを少し切り取ります)。

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

コンパイラはこれを次のようなものに変換します。

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(実際には、変数fは作成されます。fは「デリゲート」(=関数ポインタ)ですが、このデリゲートはまだtheClosureObjectオブジェクトに関連付けられています-わかりにくいもののために、この部分は省略しました。 C#を使用)

この変換は非常に大規模で扱いにくいものです。クロージャー内のクロージャーと、クロージャーと他のC#言語機能との相互作用を検討してください。Java 7にはすでに多くの新機能が含まれているため、この機能はJavaにプッシュバックされたと想像できます。


これがどこに向かっているのかがわかります。複数のクロージャーを持ち、メインスコープが同じ変数にアクセスするのは面倒です。
ラファエル

正直に言うと、これはクロージャーを実装するために既存のオブジェクト指向フレームワークを使用し、クロージャーに実際の問題があるためです。他の言語では、変数をメソッドのない独立した構造に割り当てるだけで、必要に応じて複数のクロージャーで変数を共有できます。
hugomg

@Raphael:クロージャー内のクロージャーについてどう思いますか?上のハングは、私がであることを追加してみましょう。
アレックス10ブリンク

5

質問の一部に答えるため。MorrisettとHarperによって記述された形式は、クロージャーを含む高次の多相言語の大小のセマンティクスをカバーしています。これらの前に、探している種類のセマンティクスを提供する論文があります。たとえば、SECDマシンを見てください。可変参照または可変ローカルをこれらのセマンティクスに追加するのは簡単です。そのようなセマンティクスの提供には技術的な問題があるとは思わない。


参考にしていただきありがとうございます!軽い読書には向いていないようですが、おそらくセマンティクスの論文から予想されるでしょう。
ラファエル

1
@Raphael:おそらくもっと簡単なものがあります。何かを見つけて、あなたに返事をしようとします。いずれにせよ、図8には探しているセマンティクスがあります。
デイブクラーク

大まかな概要を説明することができます。あなたの答えの中心的なアイデアは?
ラファエル

2
@ラファエル。おそらく、プログラミング言語コースで使用する講義ノートを参照して、簡単に紹介することができます。配布資料8および9見上げるください
ウダイレディ

1
そのリンクは、死んでいるか、非表示の認証の背後に表示されます。(cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf)。403は禁止されています。
ベンフレッチャー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.