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

Scala におけるモナドって何だろう?

Scala におけるモナドって何だろう?

Scala関西Summit 2019 の登壇資料です。

変更履歴
v1.0.0
v0.1.1 - init

Naoki Aoyama - @aoiroaoino

October 26, 2019
Tweet

More Decks by Naoki Aoyama - @aoiroaoino

Other Decks in Programming

Transcript

  1. Agenda ➢ Introduction ◦ モナドは身近な存在 ➢ Scala におけるモナドの基本 ◦ モナドって何?実用上の価値とは?

    ◦ 抽象的なモナドと具体的なモナド ➢ モナドを活用するために ◦ いくつかの具体的なモナドの紹介 ◦ 抽象的なモナドの活用方法 ➢ Conclusion
  2. モナドは身近な存在 Option("42").flatMap(_.toIntOption) // res25: Option[Int] = Some(42) for { i

    <- List(1, 2, 3) j <- List(1, -1) } yield i * j // res26: List[Int] = List(1, -1, 2, -2, 3, -3)
  3. モナドって何? // M[_] はモナドの型 def pure[A](a: A): M[A] def flatMap[B](f:

    A => M[B]): M[B] // map はデフォルト実装が与えられる def map[B](f: A => B): M[B] = flatMap(a => pure(f(b)))
  4. モナドって何? // M[_] はモナドの型 def pure[A](a: A): M[A] def flatMap[B](f:

    A => M[B]): M[B] // モナド則 pure(a).flatMap(f) == f(a) // 左単位元 fa.flatMap(a => pure(a)) == fa // 右単位元 fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(g)) // 結合律
  5. Scala における実用上のモナドの価値 val xs: Either[String, Int] = Right(1) val ys:

    Either[String, Int] = Right(2) for { x <- xs y <- ys } yield x + y // res0: scala.util.Either[String,Int] = Right(3)
  6. Scala における実用上のモナドの価値 val xs: List[String] = List("foo", "bar") val ys:

    List[String] = List("!", "?") for { x <- xs y <- ys } yield x + y // res3: List[String] = List(foo!, foo?, bar!, bar?)
  7. Scala における実用上のモナドの価値 for { x <- expr1 y <- expr2

    } yield { expr3 } expr1.flatMap { x => expr2.map { y => expr3 } }
  8. 【抽象的なモナドの実装】モナド型クラスの定義 trait Monad[F[_]] { def pure[A](a: A): F[A] def flatMap[A,

    B](fa: F[A])(f: A => F[B]): F[B] // デフォルト実装 def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a)) } object Monad { // implicitly[Monad[F]] のエイリアス def apply[F[_]](implicit M: Monad[F]): Monad[F] = M }
  9. 【抽象的なモナドの実装】データ型の例 sealed abstract class Maybe[A] case class Just[A](a: A) extends

    Maybe[A] case class Empty[A]() extends Maybe[A] object Maybe { // smart constructor def just[A](a: A): Maybe[A] = Just(a) def empty[A]: Maybe[A] = Empty() }
  10. 【抽象的なモナドの実装】インスタンスを定義 implicit val maybeInstances: Monad[Maybe] = new Monad[Maybe] { def

    pure[A](a: A): Maybe[A] = Just(a) def flatMap[A, B](fa: Maybe[A])(f: A => Maybe[B]): Maybe[B] = fa match { case Just(a) => f(a) case Empty() => Empty() } }
  11. 【抽象的なモナドの実装】動作確認 scala> val n = Monad[Maybe].pure(42) n: Maybe[Int] = Just(42)

    scala> Monad[Maybe].flatMap(n) { i => | if (i > 0) Maybe.just(i) else Maybe.empty[Int] | } res7: Maybe[Int] = Just(42)
  12. 【抽象的なモナドの実装】モナド則を満たすか trait MonadLaw[F[_]] { def M: Monad[F] def leftIdentity[A, B](a:

    A, f: A => F[B]): Boolean = M.flatMap(M.pure(a))(f) == f(a) def rightIdentity[A](fa: F[A]): Boolean = M.flatMap(fa)(M.pure) == fa def associativity[A, B, C](fa: F[A], f: A => F[B], g: B => F[C]): Boolean = M.flatMap(M.flatMap(fa)(f))(g) == M.flatMap(fa)(a => M.flatMap(f(a))(g)) }
  13. 【抽象的なモナドの実装】モナド則を満たすか class MonadSpec extends Properties("Monad") { def law: Monad.Law[Maybe] =

    new Monad.Law[Maybe] { override val M = Maybe.maybeInstances } def odd(i: Int): Maybe[Int] = if (i % 2 != 0) Just(i) else Empty() def fizz(i: Int): Maybe[Int] = if (i % 3 == 0) Just(i) else Empty() def buzz(i: Int): Maybe[Int] = if (i % 5 == 0) Just(i) else Empty() // ... }
  14. 【抽象的なモナドの実装】モナド則を満たすか class MonadSpec extends Properties("Monad") { def law: Monad.Law[Maybe] =

    // 左単位元, 右単位元 property("leftIdentity") = Prop.forAll { i: Int => law.leftIdentity(i, fizz) } property("rightIdentity") = Prop.forAll { i: Int => law.rightIdentity(buzz(i)) } // ... }
  15. 【抽象的なモナドの実装】モナド則を満たすか class MonadSpec extends Properties("Monad") { def law: Monad.Law[Maybe] =

    // ... // 結合律 property("associativity") = Prop.forAll { i: Int => law.associativity(odd(i), fizz, buzz) } }
  16. 【抽象的なモナドの実装】モナド則を満たすか sbt:fn> test [info] + Monad.associativity: OK, passed 100 tests.

    [info] + Monad.rightIdentity: OK, passed 100 tests. [info] + Monad.leftIdentity: OK, passed 100 tests. [info] Passed: Total 3, Failed 0, Errors 0, Passed 3
  17. 【抽象的なモナドの実装】for 式で合成するために // for 式で合成ができない for { i <- Maybe.just(1)

    j <- Maybe.just(2) } yield i + j // i <- Maybe.just(1) // ^ // On line 2: error: value flatMap is not a member of Maybe[Int] // j <- Maybe.just(2) // ^ // On line 3: error: value map is not a member of Maybe[Int]
  18. 【抽象的なモナドの実装】for 式で合成するために // Monad[Maybe].map(fa)(f) を fa.map(f) と書けるようになる trait MonadSyntax {

    implicit def toMonadOps[F[_], A](fa: F[A]): MonadOps[F, A] = new MonadOps(fa) } class MonadOps[F[_], A](val fa: F[A]) extends AnyVal { def map[B](f: A => B)(implicit M: Monad[F]): F[B] = M.map(fa)(f) def flatMap[B](f: A => F[B])(implicit M: Monad[F]): F[B] = M.flatMap(fa)(f) }
  19. 【具体的なモナドの実装】実装方法 sealed abstract class Maybe[A] { def flatMap[B](f: A =>

    Maybe[B]): Maybe[B] = this match { case Just(a) => f(a) case Empty() => Empty() } // デフォルト実装 def map[B](f: A => B): Maybe[B] = flatMap(a => Maybe.pure(f(a))) } case class Just[A](a: A) extends Maybe[A] case class Empty[A]() extends Maybe[A]
  20. 具体的なモナドの例 ❖ Option[A] ❖ Either[E, A] ❖ List[A] ❖ Reader[R,

    A] ❖ Writer[W, A] ❖ State[S, A] ❖ Cont{R, A] ❖ IO[A] ❖ Free[F[_], A] ❖ ...etc
  21. 【具体的なモナド】Reader モナド object Reader { def pure[R, A](a: A): Reader[R,

    A] = Reader(_ => a) } final case class Reader[R, A](run: R => A) { def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = Reader(r => f(run(r)).run(r)) def map[B](f: A => B): Reader[R, B] = flatMap(a => Reader.pure(f(a))) }
  22. 【具体的なモナド】Reader モナド // for 式での合成時には計算は行われていない scala> val r = for

    { | i <- Reader[String, Int]{ s => (s.toInt + 1).tap(println) } | j <- Reader[String, Int]{ s => (s.toInt * 10).tap(println) } | } yield i + j r: fn.data.Reader[String,Int] = Reader(...) scala> r.run("1") // .run で引数を与えたタイミングで実行される 2 10 res13: Int = 12
  23. 【具体的なモナド】Cont モナド(継続モナド) object Cont { def pure[R, A](a: A): Cont[R,

    A] = Cont(ar => ar(a)) } final case class Cont[R, A](run: (A => R) => R) { def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont(br => run(a => f(a).run(br)) def map[B](f: A => B): Cont[R, B] = flatMap(a => Cont.pure(f(a))) }
  24. 【具体的なモナド】Cont モナド(継続モナド) def add[R](i: Int, j: Int): Cont[R, Int] =

    Cont(ar => ar(i + j)) def mul[R](i: Int, j: Int): Cont[R, Int] = Cont(ar => ar(i * j)) def show[R](i: Int): Cont[R, String] = Cont(ar => ar(s"num: $i")) def prog[R]: Cont[R, String] = for { a <- add(1, 2) b <- mul(a, 3) s <- show(b) } yield { s.toUpperCase }
  25. 【具体的なモナド】Cont モナド(継続モナド) scala> prog.run(s => s.toList) res18: List[Char] = List(N,

    U, M, :, , 9) scala> prog.run(s => s.length) res19: Int = 6 scala> prog.run(s => s) res20: String = NUM: 9
  26. 【具体的なモナド】IO モナド object IO { def pure[A](a: A): IO[A] =

    IO(a) def apply[A](a: => A): IO[A] = new IO[A] { def unsafeRun(): A = a } } sealed abstract class IO[A] { def unsafeRun(): A // 呼び出したタイミングで全ての計算が実行される def flatMap[B](f: A => IO[B]): IO[B] = IO { f(unsafeRun()).unsafeRun() } def map[B](f: A => B): IO[B] = flatMap(a => IO.pure(f(a))) }
  27. 【具体的なモナド】IO モナド def readLine: IO[String] = IO { StdIn.readLine }

    def println(s: String): IO[Unit] = IO { Predef.println(s) } scala> val io = println("Hello!") io: fn.data.IO[Unit] = fn.data.IO$$anon$1@30609427 scala> io.unsafeRun() Hello!
  28. 【具体的なモナド】IO モナド scala> val app = for { | _

    <- println("Message:") | s <- readLine | _ <- println(s"Your message is $s") | } yield s app: fn.data.IO[String] = fn.data.IO$$anon$1@63a536db scala> app.unsafeRun() Message: Your message is Hello! res15: String = Hello!
  29. 【抽象的なモナド】MonadTransformer val prog: IO[Option[Int]] = for { // IO[A] の合成

    nOpt <- readInt mOpt <- readInt } yield for { // Option[A] の合成 n <- nOpt m <- mOpt } yield n + m
  30. 【抽象的なモナド】MonadTransformer scala> prog.unsafeRun() // 1 と 2 を入力 res6: Option[Int]

    = Some(3) scala> prog.unsafeRun() // 1 と a を入力 res7: Option[Int] = None
  31. 【抽象的なモナド】MonadTransformer ➢ F[_], G[_] がモナドのとき、F[G[_]] もモナドになる...とは限らない ➢ G[_] が特定の条件を満たす(具体的な)モナドの場合は F[G[_]]

    もモナドになる ◦ F[Option[A]], F[Either[E, A]], F[Cont[R, A]] など ◦ 特定の条件: F[G[_]] を G[F[_]] にできる ➢ 条件を満たす(具体的な)モナドに対応するデータ型を定義し、 それもまたモナドのインスタンスにしてしまおう ◦ OptionT[F, A], EitherT[F, E, A], ContT[F, R, A] など
  32. 【抽象的なモナド】MonadTransformer final case class OptionT[F[_], A](value: F[Option[A]]) { def map[B](f:

    A => B)(implicit M: Monad[F]): OptionT[F, B] = OptionT(M.map(value)(_.map(f))) def flatMap[B](f: A => OptionT[F, B])(implicit M: Monad[F]): OptionT[F, B] = OptionT(M.flatMap(value) { case Some(a) => f(a).value case None => M.pure(None) }) }
  33. 【抽象的なモナド】MonadTransformer object IO { // ... // あらかじめ IO[A] のモナドインスタンスは定義されているとする

    implicit val ioInstances: Monad[IO] = new Monad[IO] { override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa.flatMap(f) override def pure[A](a: A): IO[A] = IO.pure(a) } }
  34. 【抽象的なモナド】MonadTransformer scala> val prog = for { | i <-

    OptionT(readInt) | j <- OptionT(readInt) | } yield i + j prog: fn.data.OptionT[fn.data.IO,Int] = OptionT(fn.data.IO$$anon$1@524114c0) scala> prog.value res2: fn.data.IO[Option[Int]] = fn.data.IO$$anon$1@524114c0
  35. 【抽象的なモナド】Effect Injection ❖ 返り値の型を F[_] に包み、(主に)実装クラスで Effect を差し替える ❖ F[_]

    に Monad の制約をかけることで for 式で書けることを約束 ❖ 巷で Tagless-final と呼ばれているパターン
  36. 【抽象的なモナド】Effect Injection class GetReplyTargetUsers[F[_]: Monad]( userRepo: UserRepository[F] messageRepo: MessageRepository[F] ){

    def run(targetMessageId: MessageId): F[Seq[User]] = for { // 前出の toMonadOps がスコープ内で定義されてること messages <- msessageRepo.resolveAll(targetMessageId) userIds = messages.flatMap(_.replyMessages.targetUser).toSet users <- userRepo.resolveAll(userIds: _*) } yield users }
  37. 【抽象的なモナド】Effect Injection val implByIO: GetReplyTargetUsers[IO] = ??? implByIO.run(messageId) // =>

    IO[Seq[User]] type Aync[A] = Reader[ExecutionContext, Future[A]] val implByAsync: GetReplyTargetUsers[Async] = ??? implByAsync.run(messageId) // => Async[Seq[User]] // => Reader[ExecutionContext, Future[Seq[User]]]
  38. 【抽象的なモナド】Effect Injection - メリット ❖ Monad 型クラスのインスタンスが定義されていれば F[_] に当てはめられる =>

    実行する際の「文脈」をインターフェースから切りせる => あとで(実装するタイミングで)差し替えられる