1. 序章

FugueはAtlassianによるJavaライブラリです。 これは、関数型プログラミングをサポートするユーティリティのコレクションです。

この記事では、最も重要なFugueのAPIに焦点を当てて調査します。

2. 遁走入門

プロジェクトでFugueの使用を開始するには、次の依存関係を追加する必要があります。

<dependency>
    <groupId>io.atlassian.fugue</groupId>
    <artifactId>fugue</artifactId>
    <version>4.5.1</version>
</dependency>

Fugueの最新バージョンはMavenCentralで見つけることができます。

3. オプション

java.util.Optional。に対するFugueの答えであるOptionクラスを見て旅を始めましょう。

名前から推測できるように、Optionは潜在的に存在しない値を表すコンテナです。

つまり、オプションは、特定のタイプの一部の値、またはなしのいずれかです。

Option<Object> none = Option.none();
assertFalse(none.isDefined());

Option<String> some = Option.some("value");
assertTrue(some.isDefined());
assertEquals("value", some.get());

Option<Integer> maybe = Option.option(someInputValue);

3.1. マップ操作

標準の関数型プログラミングAPIの1つは、 map()メソッドです。これにより、提供された関数を基になる要素に適用できます。

このメソッドは、提供された関数が存在する場合、Optionの値に適用します。

Option<String> some = Option.some("value") 
  .map(String::toUpperCase);
assertEquals("VALUE", some.get());

3.2. オプションおよびNull

名前の違いに加えて、Atlassianはオプションに対してオプションとは異なるいくつかの設計上の選択を行いました。 それらを見てみましょう。

null値を保持する空でないオプションを直接作成することはできません

Option.some(null);

上記は例外をスローします。

ただし、 map()操作を使用した結果として取得できます。

Option<Object> some = Option.some("value")
  .map(x -> null);
assertNull(some.get());

これは、java.util.Optional。を使用するだけでは不可能です。

3.3. オプションIsイテレータ

オプションは、最大1つの要素を保持するコレクションとして扱うことができるため、Iterableインターフェイスを実装することは理にかなっています。

これにより、コレクション/ストリームを操作する際の相互運用性が大幅に向上します。

そして今、例えば、別のコレクションと連結することができます:

Option<String> some = Option.some("value");
Iterable<String> strings = Iterables
  .concat(some, Arrays.asList("a", "b", "c"));

3.4. オプションからストリームへの変換

オプション反復可能であるため、ストリームにも簡単に変換できます。

変換後、 Stream インスタンスには、オプションが存在する場合は正確に1つの要素が含まれ、それ以外の場合はゼロになります。

assertEquals(0, Option.none().toStream().count());
assertEquals(1, Option.some("value").toStream().count());

3.5. java.util.Optional相互運用性

標準のOptional実装が必要な場合は、 toOptional()メソッドを使用して簡単に取得できます。

Optional<Object> optional = Option.none()
  .toOptional();
assertTrue(Option.fromOptional(optional)
  .isEmpty());

3.6. オプションユーティリティクラス

最後に、Fugueは、適切な名前のOptionsクラスのOptionを操作するためのいくつかのユーティリティメソッドを提供します。

これは、コレクションから空のOptionsを削除するためのfilterNoneや、Optionsのコレクションをingするためのflattenなどのメソッドを備えています。 を囲まれたオブジェクトのコレクションに入れ、空のオプションを除外します。

さらに、それはのいくつかのバリエーションを備えていますリフト持ち上げる方法関数関数

Function<Integer, Integer> f = (Integer x) -> x > 0 ? x + 1 : null;
Function<Option<Integer>, Option<Integer>> lifted = Options.lift(f);

assertEquals(2, (long) lifted.apply(Option.some(1)).get());
assertTrue(lifted.apply(Option.none()).isEmpty());

これは、Optionを認識しない関数をOptionを使用するメソッドに渡したい場合に便利です。

map methodと同様に、liftはnullをNoneにマップしないことに注意してください:

assertEquals(null, lifted.apply(Option.some(0)).get());

4. 2つの可能な結果を伴う計算用ののいずれか

これまで見てきたように、オプションクラスを使用すると、値の欠如を機能的に処理できます。

ただし、「値なし」よりも多くの情報を返す必要がある場合があります。 たとえば、正当な値またはエラーオブジェクトのいずれかを返したい場合があります。

いずれかのクラスは、そのユースケースをカバーします。

いずれかののインスタンスは、 RightまたはLeftにすることができますが、同時ににすることはできません。

慣例により、右は計算が成功した結果であり、左は例外的なケースです。

4.1. どちらかを構築する

2つの静的ファクトリメソッドのいずれかを呼び出すことで、いずれかのインスタンスを取得できます。

Right を含むどちらかが必要な場合は、rightと呼びます。

Either<Integer, String> right = Either.right("value");

それ以外の場合は、leftと呼びます。

Either<Integer, String> left = Either.left(-1);

ここで、計算ではStringまたはIntegerを返すことができます。

4.2. いずれかを使用する

いずれかのインスタンスがある場合、それが左か右かを確認し、それに応じて動作することができます。

if (either.isRight()) {
    ...
}

さらに興味深いことに、機能的なスタイルを使用して操作を連鎖させることができます。

either
  .map(String::toUpperCase)
  .getOrNull();

4.3. 投影

Option、Try、のような他のモナドツールとの主な違いは、多くの場合、偏りがないという事実です。 簡単に言えば、map()メソッドを呼び出すと、どちらか側と側のどちらで動作するかわかりません。

これは、予測が役立つところです。

左と右の投影は、それぞれ左または右の値に焦点を合わせたEitherのスペキュラービューです。

either.left()
  .map(x -> decodeSQLErrorCode(x));

上記のコードスニペットで、 EitherLeftの場合、decodeSQLErrorCode()が基になる要素に適用されます。 どちらか右の場合、はそうではありません。 正しい投影法を使用する場合も同様です。

4.4. ユーティリティメソッド

Options と同様に、Fugueは Eithers のユーティリティでいっぱいのクラスも提供し、そのように呼ばれます:Eithers

これには、 Either のコレクションをフィルタリング、キャスト、および反復するためのメソッドが含まれています。

5. Tryによる例外処理

Try と呼ばれる別のバリエーションを使用して、Fugueのデータ型のツアーを終了します。

TryEitherに似ていますが、例外の処理専用であるという点で異なります。

オプションと同様に、いずれかとは異なり、 Try は単一のタイプでパラメーター化されます。これは、「その他」のタイプが例外に固定されているためです( オプションの場合、暗黙的に Void )です。

したがって、 Try は、SuccessまたはFailureのいずれかになります。

assertTrue(Try.failure(new Exception("Fail!")).isFailure());
assertTrue(Try.successful("OK").isSuccess());

5.1. 試行のインスタンス化

多くの場合、成功または失敗としてTryを明示的に作成することはありません。 むしろ、メソッド呼び出しから作成します。

Checked.of は指定された関数を呼び出し、その戻り値またはスローされた例外をカプセル化するTryを返します。

assertTrue(Checked.of(() -> "ok").isSuccess());
assertTrue(Checked.of(() -> { throw new Exception("ko"); }).isFailure());

別のメソッドChecked.liftは、スローする可能性のある関数を受け取り、それをTryを返す関数にliftsします。

Checked.Function<String, Object, Exception> throwException = (String x) -> {
    throw new Exception(x);
};
        
assertTrue(Checked.lift(throwException).apply("ko").isFailure());

5.2. での作業を試す

Try を取得したら、最終的にそれを使用して実行したい3つの最も一般的なことは次のとおりです。

  1. その価値を抽出する
  2. いくつかの操作を成功した値に連鎖させる
  3. 関数で例外を処理する

さらに、明らかに、 Try を破棄するか、他のメソッドに渡す場合、上記の3つだけがオプションではありませんが、他のすべての組み込みメソッドは、これら3つよりも便利です。

5.3. 成功した価値の抽出

値を抽出するには、getOrElseメソッドを使用します。

assertEquals(42, failedTry.getOrElse(() -> 42));

存在する場合は成功した値を返し、存在しない場合は計算値を返します。

getOrThrow などはありませんが、 getOrElse は例外をキャッチしないため、簡単に次のように記述できます。

someTry.getOrElse(() -> {
    throw new NoSuchElementException("Nothing to get");
});

5.4. 成功後の通話の連鎖

関数スタイルでは、最初に明示的に抽出せずに、成功値(存在する場合)に関数を適用できます。

これは、オプション 、およびその他のほとんどのコンテナとコレクションに見られる典型的なマップメソッドです。

Try<Integer> aTry = Try.successful(42).map(x -> x + 1);

Try を返すので、さらに操作を連鎖させることができます。

もちろん、flatMapのバリエーションもあります。

Try.successful(42).flatMap(x -> Try.successful(x + 1));

5.5. 例外からの回復

成功した値ではなく、 Try (存在する場合)を除いて機能する類似のマッピング操作があります。

ただし、これらの方法は、その意味が例外から回復する、すなわち 成功するトライを生み出すためにデフォルトの場合。

したがって、restoreを使用して新しい値を生成できます。

Try<Object> recover = Try
  .failure(new Exception("boo!"))
  .recover((Exception e) -> e.getMessage() + " recovered.");

assertTrue(recover.isSuccess());
assertEquals("boo! recovered.", recover.getOrElse(() -> null));

ご覧のとおり、recovery関数は例外を唯一の引数として取ります。

回復関数自体がスローした場合、結果は別の失敗したTryです。

Try<Object> failure = Try.failure(new Exception("boo!")).recover(x -> {
    throw new RuntimeException(x);
});

assertTrue(failure.isFailure());

flatMapに類似したものはrecoverWithと呼ばれます。

Try<Object> recover = Try
  .failure(new Exception("boo!"))
  .recoverWith((Exception e) -> Try.successful("recovered again!"));

assertTrue(recover.isSuccess());
assertEquals("recovered again!", recover.getOrElse(() -> null));

6. その他のユーティリティ

まとめる前に、Fugueの他のユーティリティのいくつかを簡単に見てみましょう。

6.1. ペア

ペアは、Fugueがと呼ぶ2つの等しく重要なコンポーネントで構成された、非常にシンプルで用途の広いデータ構造です。

Pair<Integer, String> pair = Pair.pair(1, "a");
        
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());

Fugueは、マッピングと適用可能なファンクターパターン以外に、Pairに多くの組み込みメソッドを提供していません。

ただし、ペアはライブラリ全体で使用されており、ユーザープログラムですぐに利用できます。

次の貧しい人のLispの実装は、ほんの数回のキーストロークです!

6.2. 単位

ユニットは、「値なし」を表すことを意味する単一の値を持つ列挙型です。

これは、voidリターンタイプと Void クラスの代わりであり、nullを廃止します。

Unit doSomething() {
    System.out.println("Hello! Side effect");
    return Unit();
}

ただし、非常に驚くべきことに、オプションは単位を理解せず、単位をなしではなく何らかの値のように扱います。

6.3. 静的ユーティリティ

記述してテストする必要のない静的ユーティリティメソッドでいっぱいのクラスがいくつかあります。

Functions クラスは、合成、アプリケーション、カリー化、 Option を使用した部分関数、弱いメモ化など、さまざまな方法で関数を使用および変換するメソッドを提供します。

サプライヤークラスは、サプライヤー用の同様の、しかしより限定されたユーティリティのコレクション、つまり引数のない関数を提供します。

最後に、IterablesおよびIteratorsには、広く使用されている2つの標準Javaインターフェースを操作するための静的メソッドのホストが含まれています。

7. 結論

この記事では、AtlassianによるFugueライブラリの概要を説明しました。

モノイド半群のような代数の多いクラスは、ジェネラリストの記事に収まらないため、触れていません。

ただし、Fugue javadocsおよびソースコードでそれらなどについて読むことができます。

また、GuavaやScalaとの統合などを提供するオプションのモジュールについても触れていません。

これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。