アトラシアンフーガの紹介
1前書き
Fugue
はAtlassianによるJavaライブラリです。それは
関数型プログラミング
をサポートするユーティリティの集まりです。
今回の記事では、最も重要なFugueのAPIに焦点を合わせて探ります。
** 2 Fugueを使ってみる
**
私たちのプロジェクトでFugueを使い始めるには、次の依存関係を追加する必要があります。
<dependency>
<groupId>io.atlassian.fugue</groupId>
<artifactId>fugue</artifactId>
<version>4.5.1</version>
</dependency>
Fugueの最新バージョンを見つけることができます。
Maven Centralへ。
** 3
オプション
**
java.util.Optional.
に対するFugueの答えである
Option
クラスを見て、旅を始めましょう。
名前から推測できるように、**
__
Optionは、潜在的に存在しない値を表すコンテナーです。
つまり、
Option
は、特定の型の
Some
値か
None
のいずれかです。
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.
map
操作+
**
標準の関数型プログラミングAPIの1つは、提供された関数を基礎となる要素に適用できるようにする
map()
メソッドです。
このメソッドは、指定された関数が
Option
‘s値に存在する場合はそれに適用します。
Option<String> some = Option.some("value")
.map(String::toUpperCase);
assertEquals("VALUE", some.get());
3.2.
Option
と
Null
Value
命名の違いに加えて、アトラシアンは
Option
に対して
Optional
とは異なるいくつかの設計上の選択を行いました。それでは、それらを見てみましょう。
-
null
値を保持する空でない
Option
を直接作成することはできません** :
Option.some(null);
上記は例外を投げます。
-
しかし、
map()
操作を使用した結果として取得することができます。
Option<Object> some = Option.some("value")
.map(x -> null);
assertNull(some.get());
単に
java.util.Optional.
を使用しただけでは、これは不可能です。
** 3.3.
Option
は__Iterable +です
__ **
Option
は、最大1つの要素を保持するコレクションとして扱うことができるので、
Iterable
インターフェースを実装することは意味があります。
これにより、コレクションやストリームを扱う際の相互運用性が大幅に向上します。
そして今、例えば、他のコレクションと連結することができます。
Option<String> some = Option.some("value");
Iterable<String> strings = Iterables
.concat(some, Arrays.asList("a", "b", "c"));
** 3.4.
Option
から
Stream
への変換
**
Option
は
Iterableなので、
Stream__にも簡単に変換できます。
変換後、
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.
Options
ユーティリティクラス+
**
最後に、Fugueは適切な名前の
Options
クラスで
__Option
__sを扱うためのユーティリティメソッドをいくつか提供しています。
コレクションから空の
Options
を削除するための
filterNone
、および
Options
のコレクションを囲まれたオブジェクトのコレクションに変換するための
flatten
などのメソッドを備えています。
さらに、
Function <A、B>
を
Function <Option <A>、Option <B>>
に持ち上げる、
lift
メソッドのいくつかの変形があります。
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
メソッドと同じように、**
lift
はnullを
None
にマップしません。
assertEquals(null, lifted.apply(Option.some(0)).get());
4. 2つの可能性のある結果を伴う計算に対して
Either
ご覧のとおり、
Option
クラスを使用すると、機能的な方法で値の欠如に対処できます。
ただし、「値なし」よりも多くの情報を返す必要がある場合があります。たとえば、正当な値またはエラーオブジェクトを返すことができます。
Either
クラスはそのユースケースをカバーします。
-
Either
のインスタンスは
Right
または__Leftになることができますが、同時に両方にすることはできません
慣例により、右側は計算が成功した結果であり、左側は例外的な場合です。
4.1.
Either
を構築する
2つの静的ファクトリメソッドの1つを呼び出すことで
Either
インスタンスを取得できます。
Right
値
__を含む
Either
が必要な場合は、
right__を呼び出します。
Either<Integer, String> right = Either.right("value");
そうでなければ、
left
を呼び出します。
Either<Integer, String> left = Either.left(-1);
ここでは、計算は
String
または
Integer.
を返すことができます。
4.2.
Either
を使う
Either
インスタンスがあるとき、それが左か右かを確認し、それに従って行動することができます。
if (either.isRight()) {
...
}
さらに興味深いことに、機能的なスタイルを使って操作を連鎖させることができます。
either
.map(String::toUpperCase)
.getOrNull();
4.3. 予測
Option、Tryなどの他のモナドツールとの違いは、偏りがないことです。簡単に言うと、map()メソッドを呼び出した場合、
Either
は
Left
と
Right__のどちらの側で作業するのかわからないのです。
これが予測が役に立つところです。
-
左と右の射影は、それぞれ左または右の値に焦点を合わせる
Either
の鏡面図です。
either.left()
.map(x -> decodeSQLErrorCode(x));
上記のコードスニペットでは、
Either
が
Leftの場合、decodeSQLErrorCode()
が基になる要素に適用されます。
Either
が
Rightの場合、
それはしません。正しい投影法を使用しているときも同じです。
** 4.4. 効用メソッド
**
Options
と同様に、Fugueも
Eithers
のためのユーティリティでいっぱいのクラスを提供します、そしてそれはそのように呼ばれます:
Eithers
。
これには、
__Either
__のコレクションをフィルタリング、キャスト、および反復するためのメソッドが含まれています。
5.
Try
を使った例外処理
私たちはFugueでのthis-this-or-thatデータ型のツアーを
Try
という別のバリエーションで締めくくります。
Try
は
Either
と似ていますが、例外を扱うためのものです。
Option
や
Either
とは異なり、
Try
は単一の型でパラメータ化されています。これは、「その他」の型は
Exception
に固定されているためです(
Option
では暗黙のうちに
Void
)。
したがって、
Try
は
Success
または
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());
もう1つのメソッド
Checked.lift
は、スローされる可能性のある関数を受け取り、それを
Try
を返す関数に
lift
します。
Checked.Function<String, Object, Exception> throwException = (String x) -> {
throw new Exception(x);
};
assertTrue(Checked.lift(throwException).apply("ko").isFailure());
** 5.2.
Try
を使った作業
**
Try
を入手したら、最終的にそれを使用したいと思う3つの最も一般的なことがあります。
-
その価値を引き出す
-
成功した値にいくつかの操作を連鎖する
-
関数で例外を処理する
そのうえ、明らかに、
Try
を破棄したり他のメソッドに渡したりする場合、上記の3つが唯一の選択肢ではありませんが、他のすべての組み込みメソッドはこれら3つのメソッドよりも便利です。
** 5.3. 成功する価値を引き出す+
**
値を抽出するには、
getOrElse
メソッドを使用します。
assertEquals(42, failedTry.getOrElse(() -> 42));
存在する場合は成功値、それ以外の場合は何らかの計算値を返します。
getOrThrow
などはありませんが、
getOrElse
は例外をキャッチしないため、簡単に記述できます。
someTry.getOrElse(() -> {
throw new NoSuchElementException("Nothing to get");
});
** 5.4. 成功後の呼び出しの連鎖+
**
機能的スタイルでは、最初に明示的に抽出することなく、成功値(存在する場合)に関数を適用できます。
これは、
Option
、
Either
、その他のほとんどのコンテナおよびコレクションに見られる典型的な
map
メソッドです。
Try<Integer> aTry = Try.successful(42).map(x -> x + 1);
それは
Try
を返すので、我々はさらなる操作を連鎖することができる。
もちろん、
flatMap
もあります。
Try.successful(42).flatMap(x -> Try.successful(x + 1));
** 5.5. 例外からの回復
**
成功した値ではなく、
Try
(存在する場合)を除いて機能する類似のマッピング操作があります。
しかしながら、それらの方法は、それらの意味が例外から回復すること、すなわちデフォルトの場合に成功した
Try
** を生成することであるという点で異なる。
したがって、
recover
を使用して新しい値を生成できます。
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. ペア
Pair
は、Fugueが
left
および
right
と呼ぶ2つの同様に重要なコンポーネントで構成された、非常にシンプルで用途の広いデータ構造です。
Pair<Integer, String> pair = Pair.pair(1, "a");
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());
Fugueは
__Pair
__sには、マッピングや適切なファンクターパターン以外にも多くの組み込みメソッドを提供していません。
ただし、
__ Pair
__はライブラリ全体で使用されており、ユーザープログラムですぐに使用できます。
次の貧しい人によるLispの実装は、ほんの数回のキーストロークです。
6.2. 単位
Unit
は、「値なし」を表すことを意図した単一の値を持つenumです。
これはvoidの戻り値の型と
Void
クラスの代わりになり、
null
がなくなります。
Unit doSomething() {
System.out.println("Hello! Side effect");
return Unit();
}
しかし、驚くべきことに、**
Option
は
Unit
を理解していないため、値をnoneの代わりにある値のように扱います。
** 6.3. 静的ユーティリティ
**
作成してテストする必要のない静的なユーティリティメソッドが満載のクラスがいくつかあります。
Functions
クラスは、さまざまな方法で関数を使用および変換するメソッドを提供します。構成、アプリケーション、カリー化、
Option
を使用した部分関数、弱いメモ化など。
Suppliers
クラスは、同様の、しかしより限定された、
__Supplier
__のためのユーティリティのコレクション、すなわち引数のない関数を提供します。
最後に、
Iterables
と
Iterators
には、広く使用されているこれら2つの標準Javaインターフェースを操作するための多数の静的メソッドが含まれています。
7. 結論
この記事では、AtlassianによるFugueライブラリの概要を説明しました。
Monoid
や
Semigroups
のような代数的に重いクラスは、一般論者の記事には収まらないので、触れていません。
しかし、それらについてはFugue
javadocs
およびhttps://bitbucket.org/atlassian/fugueで読むことができます。/src[ソースコード]
また、GuavaやScalaとの統合など、オプションのモジュールについても触れていません。
これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/libraries[GitHubプロジェクト]で見つけることができます – これはMavenプロジェクトです。そのまま実行します。