Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 7/25 なるほど、失敗する可能性がある処理の戻り値は Future[Either[A, B]] にすればいいんだな? 7 / 25

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 9/25 ドキュメントとか人の言うことに 素直に従っただけなのに... もしかしてPlayFramework 辛すぎ? 9 / 25

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 13/25 Future とEither の成功と失敗の意味を考える。 Future とEither で表現される失敗は何が違う? 業務的な例外はEither それ以外の非チェック的な例外はFuture => 業務的に注目すべきはEither の部分だけ? 13 / 25

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 15/25 もしかして、Future を隠蔽して Either の成功・失敗に集中できるようなラッパーを作れば良い? 15 / 25

Slide 16

Slide 16 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 16/25 ラッパーをfor 式で使うにはmap やflatMap を実装する必要があるけど Future が失敗のときはそのまま Future が成功してEither が失敗のときもそのまま Future が成功してEither も成功したときだけmap する となるように実装してあげれば良さそう。 16 / 25

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 19/25 Q. 毎回自分で実装しなきゃいけないの? 19 / 25

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

2019/4/19 PlayFramework 戻り値の型どうするのか問題 127.0.0.1:8080/PlayScalaResult/#1 22/25 実は今日のテーマは Either のモナドトランスフォーマーEitherT を使ってPlayFramework の 戻り値の型をどうするかという問題に対処したよ、という話でした。 22 / 25

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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