アトラシアンフーガの紹介

アトラシアンフーガの概要

1. 前書き

Fugueは、AtlassianによるJavaライブラリです。これは、Functional Programmingをサポートするユーティリティのコレクションです。

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

2. フーガ入門

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


    io.atlassian.fugue
    fugue
    4.5.1

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

3. Option

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

名前から推測できるように、Option's a container representing a potentially absent value.

言い換えると、Optionは、特定のタイプのSome値またはNoneのいずれかです。

Option none = Option.none();
assertFalse(none.isDefined());

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

Option maybe = Option.option(someInputValue);


3.1. map操作

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

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

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

3.2. OptionおよびNull

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

We cannot directly create a non-empty Option holding a null value

Option.some(null);

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

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

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


単にjava.util.Optional.を使用する場合、これは不可能です。

3.3. OptionIterableです

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

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

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

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

3.4. OptionStreamに変換する

OptionIterable,であるため、Streamに簡単に変換することもできます。

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

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

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

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

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



3.6. Optionsユーティリティクラス

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

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

さらに、Function<A,B>Function<Option<A>, Option<B>>にリフトするliftメソッドのいくつかのバリエーションを備えています。

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

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

これは、Optionを使用するメソッドにOptionを認識しない関数を渡したい場合に役立ちます。

mapメソッドと同様に、lift doesn’t map null to None:であることに注意してください。

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

4. 2つの可能な結果を​​伴う計算のEither

これまで見てきたように、Optionクラスを使用すると、値がない場合に機能的に対処できます。

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

Eitherクラスはそのユースケースをカバーします。

Eitherのインスタンスは、RightまたはLeft but never both at the same timeにすることができます。

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

4.1. Eitherの構築

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

Right:を含むEitherが必要な場合は、rightを呼び出します。

Either right = Either.right("value");

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

Either left = Either.left(-1);

ここで、計算はStringまたはInteger.のいずれかを返すことができます

4.2. Eitherの使用

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

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

さらに興味深いことに、機能的なスタイルを使用して操作をチェーンできます。

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

4.3. プロジェクション

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

これは、予測が便利な場所です。

それぞれLeft and right projections are specular views of an Either that focus on the left or right value

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

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

4.4. 効用メソッド

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

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

5. Tryによる例外処理

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

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

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

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

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

5.1. Tryのインスタンス化

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

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

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

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

Checked.Function throwException = (String x) -> {
    throw new Exception(x);
};

assertTrue(Checked.lift(throwException).apply("ko").isFailure());

5.2. Tryの操作

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. 成功後の通話の連鎖

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

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

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

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

もちろん、flatMapの種類もあります。

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

5.5. 例外からの回復

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

ただし、これらのメソッドは、デフォルトの場合、その意味がto recover from the exception, i.e. to produce a successful Tryであるという点で異なります。

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

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

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


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

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

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

assertTrue(failure.isFailure());


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

Try 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. ペア

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

Pair pair = Pair.pair(1, "a");

assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());

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

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

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

6.2. Unit

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

これは、voidの戻り値の型とVoidクラスの代わりであり、nullを削除します。

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

しかし、非常に驚​​くべきことに、Option doesn’t understand Unit, treating it like some value instead of none.

6.3. 静的ユーティリティ

静的ユーティリティメソッドでいっぱいのクラスがいくつかあり、作成してテストする必要はありません。

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

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

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

7. 結論

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

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

ただし、Fuguejavadocsおよびsource codeでそれらなどについて読むことができます。

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

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