java-currying
Javaのカリー化
1. 前書き
Java 8以降、Javaで1パラメーター関数と2パラメーター関数を定義できるようになり、パラメーターとして渡すことで、その動作を他の関数に注入できるようになりました。 しかし、より多くのパラメーターを持つ関数については、https://www.baeldung.com/vavr [Vavr]のような外部ライブラリに依存しています。
別のオプションは、https://en.wikipedia.org/wiki/Currying [currying]を使用することです。 カリー化とlink:/java-8-functional-interfaces[functional interfaces]を組み合わせることで、ユーザーにすべての入力を強制する読みやすいビルダーを定義することもできます。
*このチュートリアルでは、カレーを定義し、その使用方法を紹介します*。
2. 簡単な例
複数のパラメーターを持つレターの具体例を考えてみましょう。
簡略化した最初のバージョンでは、本文と挨拶文のみが必要です。
class Letter {
private String salutation;
private String body;
Letter(String salutation, String body){
this.salutation = salutation;
this.body = body;
}
}
2.1. 方法による作成
このようなオブジェクトは、メソッドを使用して簡単に作成できます。
Letter createLetter(String salutation, String body){
return new Letter(salutation, body);
}
2.2. _BiFunction_を使用した作成
上記の方法は正常に機能しますが、機能的なスタイルで記述されたものにこの動作を提供する必要がある場合があります。 Java 8以降、この目的で_BiFunction_を使用できます。
BiFunction<String, String, Letter> SIMPLE_LETTER_CREATOR
= (salutation, body) -> new Letter(salutation, body);
2.3. 一連の機能を使用した作成
これを、それぞれが1つのパラメーターを持つ関数のシーケンスとして言い換えることもできます。
Function<String, Function<String, Letter>> SIMPLE_CURRIED_LETTER_CREATOR
= salutation -> body -> new Letter(salutation, body);
_salutation_が関数にマッピングされることがわかります。 結果の関数は、新しい_Letter_オブジェクトにマッピングされます。 戻り値の型が_BiFunction_からどのように変化したかを確認してください。 _Function_クラスのみを使用しています。 *このような関数のシーケンスへの変換はカリー化と呼ばれます*
3. 高度な例
カリー化の利点を示すために、_Letter_クラスコンストラクターをより多くのパラメーターで拡張しましょう。
class Letter {
private String returningAddress;
private String insideAddress;
private LocalDate dateOfLetter;
private String salutation;
private String body;
private String closing;
Letter(String returningAddress, String insideAddress, LocalDate dateOfLetter,
String salutation, String body, String closing) {
this.returningAddress = returningAddress;
this.insideAddress = insideAddress;
this.dateOfLetter = dateOfLetter;
this.salutation = salutation;
this.body = body;
this.closing = closing;
}
}
3.1. 方法による作成
前と同じように、メソッドを使用してオブジェクトを作成できます。
Letter createLetter(String returnAddress, String insideAddress, LocalDate dateOfLetter,
String salutation, String body, String closing) {
return new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}
3.2. 任意のアリティの関数
アリティは、関数が取るパラメーターの数の尺度です。 Javaは、https://www.baeldung.com/java-8-functional-interfaces [nullary](_Supplier_)、https://www.baeldung.com/java-8-functional-interfaces [unary]の既存の機能インターフェースを提供します。 (_Function_)、およびバイナリ(_BiFunction_)ですが、それだけです。 新しい機能インターフェイスを定義しないと、6つの入力パラメーターを持つ関数を提供できません。
カレーは私たちの出口です。 *任意のアリティを単項関数のシーケンスに変換します*。 したがって、この例では、次を取得します。
Function<String, Function<String, Function<LocalDate, Function<String,
Function<String, Function<String, Letter>>>>>> LETTER_CREATOR =
returnAddress
-> closing
-> dateOfLetter
-> insideAddress
-> salutation
-> body
-> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
3.3. 冗長タイプ
明らかに、上記のタイプは完全に読みやすくありません。 このフォームでは、_â€〜apply'_を6回使用して_Letter_を作成します。
LETTER_CREATOR
.apply(RETURNING_ADDRESS)
.apply(CLOSING)
.apply(DATE_OF_LETTER)
.apply(INSIDE_ADDRESS)
.apply(SALUTATION)
.apply(BODY);
3.4. 事前入力値
この一連の関数を使用して、最初の値を事前に入力し、letterオブジェクトの前方補完用の関数を返すヘルパーを作成できます。
Function<String, Function<LocalDate, Function<String, Function<String, Function<String, Letter>>>>>
LETTER_CREATOR_PREFILLED = returningAddress -> LETTER_CREATOR.apply(returningAddress).apply(CLOSING);
これを有効にするには、元の関数のパラメーターの順序を慎重に選択して、特定性の低いものが最初のものになるように注意する必要があります。*
4. ビルダーパターン
非友好的な型定義と標準の_apply_メソッドの繰り返し使用を克服するために、入力の正しい順序についての手がかりがないことを意味するため、https://www.baeldung.com/creational-design-patterns#builder [ビルダーパターン]:
AddReturnAddress builder(){
return returnAddress
-> closing
-> dateOfLetter
-> insideAddress
-> salutation
-> body
-> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}
*一連の機能の代わりに、一連の機能インターフェイスを使用します*。 上記の定義の戻り値の型は_AddReturnAddress_であることに注意してください。 以下では、中間インターフェースを定義するだけです。
interface AddReturnAddress {
Letter.AddClosing withReturnAddress(String returnAddress);
}
interface AddClosing {
Letter.AddDateOfLetter withClosing(String closing);
}
interface AddDateOfLetter {
Letter.AddInsideAddress withDateOfLetter(LocalDate dateOfLetter);
}
interface AddInsideAddress {
Letter.AddSalutation withInsideAddress(String insideAddress);
}
interface AddSalutation {
Letter.AddBody withSalutation(String salutation);
}
interface AddBody {
Letter withBody(String body);
}
したがって、これを使用して_Letter_を作成することは、一目瞭然です:_ _
Letter.builder()
.withReturnAddress(RETURNING_ADDRESS)
.withClosing(CLOSING)
.withDateOfLetter(DATE_OF_LETTER)
.withInsideAddress(INSIDE_ADDRESS)
.withSalutation(SALUTATION)
.withBody(BODY));
前と同様に、レターオブジェクトを事前に入力できます。
AddDateOfLetter prefilledLetter = Letter.builder().
withReturnAddress(RETURNING_ADDRESS).withClosing(CLOSING);
*インターフェースが充填順序を保証することに注意してください*。 したがって、単に_closing_を事前入力することはできません。
5. 結論
カリー化の適用方法を見てきましたので、標準のJava機能インターフェースでサポートされているパラメーターの数に制限はありません。 さらに、最初のいくつかのパラメーターを簡単に事前入力できます。 さらに、これを使用して読み取り可能なビルダーを作成する方法を学びました。
いつものように、完全なコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns-functional[GitHubで利用可能]です。