Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active November 24, 2021 05:54
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asufana/341921ab2b31ecf2feb0 to your computer and use it in GitHub Desktop.
Save asufana/341921ab2b31ecf2feb0 to your computer and use it in GitHub Desktop.
Java8 Streamから学ぶOptionalモナドとEitherモナド。

Java8 Stream, Optional, Either and Try.

Java8 Stream の使い方を覚えたら、Optional や Either もすぐ使えるようになるよ!編

Stream

リスト要素の有無を意識せずに処理することができる

Integer result =
	Arrays.asList(1, 2, 3)
          .stream()
          .filter(n -> n > 5) //この時点で要素がなくなる
          .mapToInt(n -> n * n) //そんなことは意識せずに処理したいことを記述する
          .sum();

⇒処理するにあたりリストの要素があろうとなかろうと意識する必要がない

リスト要素に対する処理結果がリストであっても問題ない

//戻り値がリストとなる処理があったとして、
List<Integer> someList(Integer n) {
    return IntStream.range(1, n)
	                .mapToObj(i -> Integer.valueOf(i))
					.collect(toList());
}

//その関数を利用すると、
List<List<Integer>> listListInt =
	Arrays.asList(1, 2, 3)
          .stream()
          .map(n -> someList(n)) //someList() の戻り値は List<T>
          .collect(toList()); //List<List<T>>リスト要素処理したがリストだと List<List<T>> となる

そこで

List<Integer> listInt =
	Arrays.asList(1, 2, 3)
          .stream()
          .flatMap(n -> someList(n).stream()) //List<List<T>> を List<T> に flatten(平らに)する
          .collect(toList()); //List<T>flatMapを使うことでリストのリストをらにすることができる

まとめ

  • リストモナド的に処理できる
  • リスト要素の有無や、結果がリストであることを意識せずに、処理のみを記述すれば良い!

Optional

値の有無を意識せずに処理できる

Optional<Integer> result =
	Optional.ofNullable(1)
            .map(n -> (Integer) null) //この時点でnullになる
            .map(n -> n * n); //そんなことは意識せずに処理したいことを記述する

⇒処理するにあたり、値があろうとなかろうと意識する必要がない
Integer anywayResult =
	Optional.ofNullable(1)
            .map(n -> (Integer) null) //この時点でnullになる
            .map(n -> n * n); //そんなことは意識せずに処理したいことを記述する
            .orElse(0); //もし途中でNullだったらこの値を返却することとする

⇒途中がNullになった例外処理、処理最後付与すれば

値に対する処理結果がオプションであっても問題ない

//戻り値がOptionalとなる何らかの処理があったとして、
Optional<Integer> someOption(Integer n) {
    return Optional.ofNullable(n);
}

//その関数を利用すると、
Optional<Optional<Integer>> optionOptionInt =
	Optional.ofNullable(1)
            .map(n -> someOption(n));
			//戻り値が Optional<T> なため Optional<Optoinal<T>> となる

⇒値処理した Optional だとOptional<Optional<T>> となる

そこで

Optional<Integer> optionInt =
	Optional.ofNullable(1)
			.flatMap(n -> someOption(n));
			//Optional<Optional<T>> を Optional<T> に flatten(平らに)するflatMap 使うことで Optional  Optional らにすることができる

複数のOptionalを処理する場合でも、それぞれの値の有無を意識する必要がない

Optional<Integer> n1 = Optional.ofNullable(1);
Optional<Integer> n2 = Optional.ofNullable(2);

Optional<Integer> n3 =
	n1.flatMap(i1 -> n2.map(i2 -> i1 + i2));
//n1, n2 のいずれか、あるいは両方が null だろうが意識せずに処理したいことを記述すればよい

⇒複数のOptionalを処理する場合でもそれぞれの有無意識する必要がない

まとめ

  • Optionalモナド的に処理できる
  • 値の有無や、結果の有無を意識せずに、処理のみを記述すれば良い!

Optionalは要素を1つしか保持できないListと考えると分かりやすい

  • 要素をフィルタする list.stream().filter(f)optional.filter(f)
  • 要素を取り出して処理する list.stream().map(f)optional.map(f)
  • 要素を取り出して処理する(戻り値なし) list.stream().foreach(f)optional.ifPresent(f)

Optionalを例外にも利用したい

  • Optionalでは処理結果がnullであることは分かるが、
  • nullとなったその理由(例外内容)を知ることができない
  • 例外内容に応じた振る舞いを行いたい場合には、Optionalでは役不足
  • そこで Either の登場

Either

  • Either<Left, Right> という2つの入れ物を持つ
  • ただしいずれかひとつしか値を入れることができない
  • 慣例として左に例外を、右に正常時戻り値を入れる(正しいのrightにかけている)
  • 読みは íːðɚ アクセントが前

例外の有無を意識せずに処理できる

*Java8にEitherは含まれていないので、JAVASLANGライブラリを利用

//例外が発生しうる何らかの処理があったとして、
Either<Object, Integer> someEither(Integer i) {
    // ほんとはこんな感じの処理
    // try {
    //     //compute...
    //     return Either.right(1);
    // }
    // catch (Exception e) {
    //     return Either.left(e);
    // }
	return Either.left(new RuntimeException());
}

//その関数を利用するにあたり
Either<Object, Integer> result =
	someEither(1) //この時点で例外が発生している
    	.map(n -> n * 100); //そんなことは意識せずに処理したいことを記述する

⇒例外があろうとなかろうと意識する必要がない
//上の処理の続きとして、
if (result.isRight()) {
    System.out.println("Success:" + result.right().get());
} else {
    System.out.println("Failure:" + result.left().get());
}

⇒例外有無例外内容じていを
Integer anywayResult =
	someEither(1)
		.map(n -> n * 100)
		.getOrElse(0); //もし途中で例外発生していたらこの値を返却することとする

⇒途中例外になったにデフォルトがあるのであれば最後付与する

値に対する処理の結果が例外(Either)であっても問題ない

Either<Object, Either<Object, Integer>> eitherEitherInt =
	someEither(1)
		.map(n -> someEither(n));

⇒値処理した結果がEitherだとEither<Either<T>> となるが
Either<Object, Integer> eitherInt =
	someEither(1)
		.flatMap(n -> someEither(n));

⇒flatMap 使うことで Either  Either らにすることができる

複数の Either を処理する場合でも、それぞれの例外の有無を意識する必要がない

Either<Object, Integer> e1 = someEither(1);
Either<Object, Integer> e2 = someEither(2);

Object e3 =
	e1.flatMap(n1 -> e2.map(n2 -> n1 + n2))
		.getOrElse(1);

⇒複数処理場合でもそれぞれの例外有無意識する必要がない

まとめ

  • Eitherモナド的に処理できる
  • 例外の有無や、結果が例外であることを意識せずに、処理のみを記述すれば良い!

Try

例外が発生しうる処理をEither化する

*JAVASLANGライブラリを利用

//例外が発生しうる何らかの処理があったとして、
Integer someCompute() {
    //compute...
    throw new RuntimeException();
}

//Tryを使うと処理結果をEither化できる
Either<Throwable, Integer> trySomeCompute =
	Try.of(() -> someCompute()).toEither();

そうすると、

//例外の発生を意識せずに処理に集中できる
Integer resultInt =
	Try.of(() -> someCompute())
		.toEither();
		.map(res -> res * 100)
		.getOrElse(0);

例外発生しうる処理プロセスをEither化する

//エラーになり得る複数の処理を意識せずに記述する
Either<Throwable, Integer> result =
	Try.of(() -> 1 / 1)
       .mapTry(x -> 1 / 0) //ここで例外となるが意識する必要がない
       .mapTry(x -> 1)
       .toEither();

まとめのまとめ

  • Stream から map, flatmap に慣れれば、
  • Optional, Either も同じように処理できるようになるよ
  • モナドってむずかしそうだけど、並べてみるとなんとなく雰囲気わかるでしょう
  • 何かを意識せずに処理に集中できる仕組み、という感じかな
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment