MPS 2024.3 ヘルプ

クロージャー

導入

クロージャは、基本言語の便利な拡張機能です。これらはコードをより簡潔にするだけでなく、プログラミングの関数型パラダイムの領域を通過するための手段として使用できます。プログラムでは、関数を第一級オブジェクトとして扱うことができます。変数に格納したり、引数としてメソッドに渡したり、メソッドや関数に他の関数を返させたりすることができます。MPS クロージャサポートを使用すると、自国語でクロージャを使用できます。実際、MPS 自体は、たとえばコレクション言語でクロージャを多用します。

この言語は、Java のクロージャに関する「BGGA」提案仕様に大まかに従っており、Java のラムダよりも強力です。MPS クロージャは、次の 4 つの非互換性を回避することを条件に、Java ラムダに生成されます。

  • 歩留まり操作

  • 「機能的な」抽象クラス

  • アノテーション

  • 親スコープと競合するローカル変数

それ以外の場合、クロージャは匿名の内部クラスに生成されます。生成されたソリューションのクラスパスに存在する必要があるのは、クロージャランタイム jar ファイルのみです。

機能タイプ

{ Type1, Type2... => ReturnType }

関数型宣言の簡単な例から始めましょう。パラメーターを受け入れず、値を返さない関数を宣言しています。

{=> void}

サブタイプ規則

関数型は戻り型によって共変であり、パラメーター型によって反変します。

例: 与えられた {String => Number} を受け入れるメソッドを定義しました:

public void accept_Number_from_String ({String => Number} function) { ... }

このメソッドに {Object => Integer}(Object を受け取り int を返す関数)のインスタンスを渡すことができます。

this.accept_Number_from_String ({Object o => o.hashCode();});

簡単に言うと、スーパー型のシグネチャーで約束された約束を守る限り、さまざまな実型のパラメーターと戻り値を使用できます。

Closure リテラル

Closure リテラルは、次の構造を入力するだけで作成されます: { <parameter decls> => <body> }「new」演算子は必要ありません。

結果のタイプは、これらの規則の 1 つ以上に従って計算されます。

  • ExpressionStatement の場合は最後のステートメント。

  • 式を含むステートメントを返します。

  • 利回りステートメント。

注: 単一のクロージャーリテラル内で returnyield を組み合わせるのは不可能です。

Closure の呼び出し

クロージャで呼び出すことができるメソッドは、invoke 操作だけです。入力する代わりに

closure.invoke(p1,p2);

クロージャーを呼び出すには、この操作を単純化したもの(パラメーターリストを囲む括弧)を使用することをお勧めします。

closure(p1,p2);

クロージャを起動すると、通常のメソッド呼び出しのようになります。

クロージャリテラル定義の例

{int => int} fib = { int n => n <= 1 ? n : invoke(n-1) + invoke(n-2); } {int => int} fact = { int n => int res = 1; }; while (n > 1) { res = res * n--; } res; {=> sequence<int>} closure = {=> yield 1;};

再帰

再帰のない関数型プログラミングは水なしでコーヒーを作るようなものなので、クロージャの本体内からクロージャを再帰的に呼び出す自然な方法があります。

{int => long} fact = {int n => if (n == 1) { return 1L; } else { return n * invoke(n - 1); } }; println("Factorial of 10 = " + fact(10));

クロージャーの本体内のスタンドアロン呼び出しは、現在のクロージャーを呼び出します。

Closure 変換

実用的な目的のために、単一メソッドインターフェースのインスタンスが期待される場所でクロージャリテラルを使用することができ、またその逆も可能です。

public interface Worker { String doWork (int amount); } ... Worker worker = { int amount => "Done " + amount + " work";};

生成されたコードは、無名クラスを使用したときとまったく同じです。

Worker worker = new Worker() { public String doWork(int amount) { return "Done " + amount + " work"; } };

Java が RunnableCallable、またはさまざまなオブザーバーまたはリスナークラスのインスタンスを必要とするすべての場所について考えてみてください。

println("Reported from the main thread " + Thread.currentThread()); final Runnable runnable = { => println("Reported from a thread " + Thread.currentThread()); }; Thread t = new Thread(runnable); t.start();

インターフェースと同様に、厳密に 1 つの抽象メソッドを含む抽象クラスもクロージャリテラルから適応させることができます。これは、たとえば、関数として機能する既存のインターフェースを新しいインターフェースを実装する抽象クラスに変更できる場合に、新しい API への円滑な移行に役立ちます。

収量計算書

yield ステートメントはクロージャーがコレクションを生成することを可能にします。クロールリテラルの本体内で yield ステートメントが見つかった場合、その影響は次のとおりです。

  • yield ステートメント式の型が Type の場合、クロージャリテラルの結果の型は sequence <Type> です。

  • 本体内のすべての制御ステートメントは、生成時に無限の do-while ループ内で switch ステートメントに変換されます。

  • return 文の使用は禁止されており、最後の ExpressionStatement の値は無視されます。

sequence<int> sequence = new sequence<int> ({=> yield 1;}); yield 2; yield 3;

関数を返す関数

そこに機能的な心のための関数型プログラミングの少し:

{ int , int => int } add = { int x , int y => x + y ; } ; { int => int } plusThree = { int x => x + 3 ; } ; { int => int } curriedPlusThree = this . curry ( add , 3 ) ; assert plusThree . invoke ( 1 ) equals curriedPlusThree . invoke ( 1 ) ;

curry() メソッドは次のように定義されています。

public { int => int } curry ( final { int , int => int } fun , final int y ) { return { int x => fun . invoke ( x , y ) ; } ; }

ランタイム

クロージャ言語によって生成されたコードを実行するには、ソリューションのクラスパスにクロージャランタイムライブラリを追加する必要があります。この jar ファイルには、関数型の変数と一部のユーティリティクラスをサポートするために必要な合成インターフェースが含まれています。それはに位置しています: %MPS_HOME%/core/baseLanguage/jetbrains.mps.baseLanguage.closures.runtime.jar

BGGA 提案との違い

  • 制御フローをいじることはありません。これは、クロージャリテラルの境界を超える制御フローステートメントをサポートしていないことを意味します。

  • 「早期 return」の問題はありません。MPS を使用すると、return を体内の任意の場所で使用できるようになります。

  • 歩留まりステートメント。

[1] Java プログラミング言語用のクロージャ (英語)

[2] BGGA クロージャ仕様のバージョン 0.5 は部分的にサポートされています (英語)

[3] これはもはや真実ではありません。最適化の手段として、インターフェース変換に対するクロージャリテラルのみがサポートされています。