Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PlayFrameworkでFuture[Either[A, B]]どうするのか問題/ PlayFramework return type problem

7f8e44359c99a0f4fa16541614afabce?s=47 tsatow
April 18, 2019

PlayFrameworkでFuture[Either[A, B]]どうするのか問題/ PlayFramework return type problem

PlayFrameworkを使ってると頻繁にFuture[Either[A, B]]と遭遇します。
このままだととても取り回しが辛いのですが、それをどうやって解決したか、という話を第0回ゆるふわ.scalaでしました。

(追記)
投稿してから気づいたけど、この内容だとまるで自分が解決策を考えたかのような印象与えますね。解決策を考えたのは私じゃありません。私は知ったかぶりしてるだけです。

(更に追記)
スライドのタイトルだけ変更しました。
「PlayFramework戻り値の型どうするのか問題」というタイトルが内容と合っていない気がしたので。

7f8e44359c99a0f4fa16541614afabce?s=128

tsatow

April 18, 2019
Tweet

Other Decks in Technology

Transcript

  1. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 1/25 PlayFramework でFuture[Either[A, B]] どうするのか問題 佐藤貴比呂

    1 / 25
  2. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 2/25 自己紹介 2 / 25

  3. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 3/25 名前 佐藤 貴比呂( さとう たかひろ)

    所属 株式会社アットウェア Scala 歴 1 年(2017/7~2018/6) Twitter @Satoooooooooooo 趣味 釣り/ 柔道/ チアリーディング その他 ガッツあるときYokohama.scala 開催してます 3 / 25
  4. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 4/25 イントロ 4 / 25

  5. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 5/25 Scala 初心者も少なくなかったプロジェクト。 PlayFramework 使ったらやたらとFuture[Either[A, B]]

    が出てきた。 5 / 25
  6. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 6/25 Play を普通に使うとそこらじゅうにFuture が出現 業務例外はEither で返すと良いってよく見る

    例外の詳細が型からわかるとか 網羅性検査が使えるとか 6 / 25
  7. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 7/25 なるほど、失敗する可能性がある処理の戻り値は Future[Either[A, B]] にすればいいんだな? 7

    / 25
  8. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 8/25 実際に書いてみる。 // subProcess がそれぞれFuture[Either[Error, Unit]]

    を返すとする def process(input: Input): Future[Either[Error, Unit]] for { r1 <- subProcess1(input) r2 <- r1 match { case Right(_) => subProcess2(input) case Left(err) => Future.successful(Left(err)) } _ <- r2 match { case Right(_) => subProcess3(input) case Left(err) => Future.successful(Left(err)) } } yield Right(()) } 3つの処理を順番に実行してるだけなのに なんかとても辛い。 8 / 25
  9. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 9/25 ドキュメントとか人の言うことに 素直に従っただけなのに... もしかしてPlayFramework 辛すぎ? 9

    / 25
  10. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 10/25 10 / 25

  11. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 11/25 初心者でも簡単に扱える解決策が必ずある 11 / 25

  12. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 12/25 本題 ( 如何にして対処したか) 12 /

    25
  13. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 13/25 Future とEither の成功と失敗の意味を考える。 Future とEither

    で表現される失敗は何が違う? 業務的な例外はEither それ以外の非チェック的な例外はFuture => 業務的に注目すべきはEither の部分だけ? 13 / 25
  14. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 14/25 実際、Future がフローに影響する箇所はない。 def process(input: Input):

    Future[Either[Error, Unit]] for { r1 <- subProcess1(input) r2 <- r1 match { case Right(_) => subProcess2(input) case Left(err) => Future.successful(Left(err)) } _ <- r2 match { case Right(_) => subProcess3(input) case Left(err) => Future.successful(Left(err)) } } yield Right(()) } 14 / 25
  15. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 15/25 もしかして、Future を隠蔽して Either の成功・失敗に集中できるようなラッパーを作れば良い? 15

    / 25
  16. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 16/25 ラッパーをfor 式で使うにはmap やflatMap を実装する必要があるけど Future

    が失敗のときはそのまま Future が成功してEither が失敗のときもそのまま Future が成功してEither も成功したときだけmap する となるように実装してあげれば良さそう。 16 / 25
  17. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 17/25 例えばこんな感じ? case class MyResult[+E, +R](result:

    Future[Either[E, R]]) { def map[RR](f: R => RR): MyResult[E, RR] = MyResult(result.map(_.map(f))) def flatMap[LL >: L, RR](f: R => MyResult[E, RR]): MyResult[E, RR] = MyResult { result flatMap { case Right(r) => f(r).result case Left(err) => Future.successful(Left(err)) } } } 17 / 25
  18. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 18/25 実際に使ってみる。 def process(input: Input): Future[Either[Error,

    Unit]] for { _ <- subProcess1(input) _ <- subProcess2(input) _ <- subProcess3(input) } yield Right(()) } 最初の処理と比べてだいぶすっきりしたので めでたしめでたし! 18 / 25
  19. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 19/25 Q. 毎回自分で実装しなきゃいけないの? 19 / 25

  20. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 20/25 もちろんそんなことはなくて、 私が参加したプロジェクトでは使わなかったけど、Scalaz やCats の EitherT

    を導入すれば簡単に実現できる。 type MyResult[E, R] = EitherT[Future, E, R] 20 / 25
  21. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 21/25 EitherT? 21 / 25

  22. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 22/25 実は今日のテーマは Either のモナドトランスフォーマーEitherT を使ってPlayFramework の

    戻り値の型をどうするかという問題に対処したよ、という話でした。 22 / 25
  23. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 23/25 まとめ 23 / 25

  24. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 24/25 モナドトランスフォーマーを使うと、 Play で頻出しがちなFuture[Either[A, B]] を隠蔽して業務をスッキリ記述できる。

    24 / 25
  25. 2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 25/25 ご清聴ありがとうございました 25 / 25