MPS 2019.1ヘルプ

クロージャー

導入

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


この言語は、Javaでのクロージャに関する "BGGA"提案仕様に大まかに従っています。ただし、MPSクロージャを使用してコードを実行するのにJava 7は必要ありません。実際の実装では匿名の内部クラスが使用されているため、1.5以降の最近のバージョンの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> }"新しい"オペレーターは必要ありません。

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

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

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

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

注意: リターンイールドを単一のクロージャリテラル内で組み合わせることは不可能です。

Closureの呼び出し

呼び出し操作は、クロージャに対して呼び出すことができる唯一のメソッドです: 入る代わりに

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ファイルには、関数型の変数と一部のユーティリティクラスをサポートするために必要な合成インターフェースが含まれています。それはに位置しています: /core/baseLanguage/jetbrains.mps.baseLanguage.closures.runtime.jar

BGGA提案との違い

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

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

  • yieldステートメント



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


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


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

最終更新日: 2019年7月5日