さて、私は長い間 OO 開発者として、この「新しく発見された」関数型プログラミングの世界に入ろうとしています - 少なくとも、これに関係しているので、Java の世界には存在しないようnull
にコーディングしようとしています。とモナドをいじるthrow
ことによって。(少なくとも、それらはモナドだと思います。私はまだその用語と、それを正しく使用しているかどうかに慣れていません...) 私はAtlassian Fugueライブラリから離れます。Functional Javaライブラリーを調べてみましたが、現時点で準備ができているよりもはるかに大きいです。もし fj が私が必要とすることを行い、Fugue がそうでないなら、私はそれで大賛成です。Option
Either
基本的に、私が達成しようとしているのは、このやや醜い Java コードと同等です。
InputObject input = blah();
try {
final String message = getMessage(input);
if (message != null) {
try {
final ProcessedMessage processedMessage = processMessage(message);
if (processedMessage != null) {
try {
final ProcessedDetails details = getDetails(notificationDetails);
if (details != null) {
try {
awesomeStuff(details);
} catch (AwesomeStuffException ase) {
doSomethingWithAwesomeStuffException(ase);
}
}
} catch (GetDetailsException gde) {
doSomethingWithGetDetailsException(gde);
}
}
} catch (ProcessMessageException pme) {
doSomethingWithProcessMessageException(pme);
}
}
} catch (GetMessageException gme) {
doSomethingWithGetMessageException(gme);
}
単純に、モナドを使用して、次のEither
ようなことができると期待していました。
getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
.fold(this::doSomethingWithProcessMessageException, this::getDetails)
.fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
.foldLeft(this::doSomethingWithAwesomeStuffException);
...ここで、ロジックっぽいメソッドのそれぞれが、適切な例外 (おそらくドメイン固有のエラー クラスですが、今のところ例外は十分に機能します) を左に、Option<something>
右に an を含む Each を返します。メソッドはdoSomethingWith...
、エラーに対して実行したいロギング/処理を実行します。ロジックっぽいメソッドの例は次のとおりです。
Either<GetMessageException, Option<String>> getMessage(final InputObject input) {
if (input == null) {
return Either.left(new GetMessageException("Input was null");
}
try {
return Either.right(loadMessage(input));
} catch (Exception ex) {
return Either.left(new GetMessageException(ex));
}
}
他のメソッドも同様に定義されます。パラメータに を使用しないことが望ましいです。Option
前のメソッドが を返した場合、メソッドは単に呼び出されませんOption.none
。
これを実際にテストすることを妨げたジェネリック型からのコンパイラエラーのラットネストは別として、論理的には、とにかく私が望むことを実際に行うようには見えません-最初の後に基本的に何もしないことを期待していましたEach.left 値が返されましたが、もっとよく見ると、Either.left の結果を次の呼び出しに渡して、さらに続けようとしていると思います。
オプションをwas
使用するだけで、目標を達成することができます。
getMessage(input).map(this::processMessage)
.map(this::getDetails)
.map(this::awesomeStuff);
残念ながら、元のブロックと同じロジックを実現するために、ロジック メソッドは次のように実装されます。
ProcessedMessage processMessage(final String message) {
try {
return doProcessing(message);
} catch (Exception ex) {
final GetMessageException gme = new GetMessageException(ex);
doSomethingWithGetMessageException(gme);
return null;
}
}
.map
結果を に持ち上げるので、ここOption
に戻るnull
とうまくいきます。残念ながら、私はまだ本質的に呼び出し元からエラー状態を隠しています (さらに悪いことに、エラーを示すために使用しています。これは、 doProcessing が潜在的に有効な理由でnull
返されるかどうかについては何も言っていません)。null
したがって、基本的には、以前の getMessage の例に沿ったメソッド シグネチャが必要です。
Either<GetMessageException, Option<String>> getMessage(final InputObject input)
戻り値を見て、「このメソッドは失敗するか、あるかもしれないし、ないかもしれない何かを返すだろう」と一目でわかるという考えが好きです。ただし、私は FP に慣れていないため、どうすればよいかわかりません。
そして、私の理解では(おそらく欠陥がありますが)、次のようなことができるということです
getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
.fold(this::doSomethingWithProcessMessageException, this::getDetails)
.fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
.foldLeft(this::doSomethingWithAwesomeStuffException);
(おそらく異なる方法で)それは
- Each.left が処理された後はいつでも処理を停止します (つまり、適切な
doSomethingWithXException
メソッドを呼び出して停止します) 。 - チェーン全体を続行します。
doSomethingWithAwesomeStuffException
への呼び出しが失敗した場合にのみ呼び出しawesomeStuff
ます。他のメソッドが失敗した場合は、それに到達しません。
私がやろうとしていることは合理的ですか?つまり、何らかの方法で可能だと確信していますが、これらのライブラリ (または私が知らない他のライブラリ) のいずれかを使用しても、これを Java 構文に押し込むのは複雑すぎますか?