Slide 1

Slide 1 text

Scala における モナドって何だろう? 2019/10/26 Scala関西Summit 2019 Naoki Aoyama - @aoiroaoino

Slide 2

Slide 2 text

❖ Naoki Aoyama ❖ Twitter/GitHub: @aoiroaoino ❖ Working at: $ whoami

Slide 3

Slide 3 text

Agenda ➢ Introduction ○ モナドは身近な存在 ➢ Scala におけるモナドの基本 ○ モナドって何?実用上の価値とは? ○ 抽象的なモナドと具体的なモナド ➢ モナドを活用するために ○ いくつかの具体的なモナドの紹介 ○ 抽象的なモナドの活用方法 ➢ Conclusion

Slide 4

Slide 4 text

話さないこと ❖ モナドの歴史や基礎理論、数学的な背景 ❖ Scala 以外のプログラミング言語におけるモナドについて ❖ 特定のライブラリにおけるモナドの実装やパフォーマンス事情

Slide 5

Slide 5 text

Introduction

Slide 6

Slide 6 text

モナドは身近な存在 ❖ 単なる自己関手の圏におけるモノイド対象?

Slide 7

Slide 7 text

モナドは身近な存在 ❖ 実は知らぬ間に多くの場面でお世話になっている

Slide 8

Slide 8 text

モナドは身近な存在 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)

Slide 9

Slide 9 text

モナドは身近な存在 ➢ Scala でプログラミングする上で必須の概念ではない ➢ モナドを知らずとも Scala らしいコードは十分書ける ➢ そもそも標準ライブラリに(抽象的な)モナドは存在しない ○ v2.12 までは FilterMonadic というそれっぽいものが存在していた ○ 「抽象的なモナド」については後ほど

Slide 10

Slide 10 text

Scala におけるモナドの基本

Slide 11

Slide 11 text

モナドって何? ❖ モナド則を満たすように pure/flatMap を実装したもの

Slide 12

Slide 12 text

モナドって何? // 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)))

Slide 13

Slide 13 text

モナドって何? // 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)) // 結合律

Slide 14

Slide 14 text

Scala における実用上のモナドの価値 ❖ データ型の「文脈」に依存しない汎用的な操作/合成を提供

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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?)

Slide 17

Slide 17 text

Scala における実用上のモナドの価値 for { x <- expr1 y <- expr2 } yield { expr3 } expr1.flatMap { x => expr2.map { y => expr3 } }

Slide 18

Slide 18 text

Scala における実用上のモナドの価値 ❖ for 式で合成する為の十分条件。モナドであれば map/flatMap が使える

Slide 19

Slide 19 text

Scala における「モナド」が示す二つの意味 ❖ 抽象的なモナド ❖ 具体的なモナド ※この区分と呼称は一般的なものではない。

Slide 20

Slide 20 text

抽象的なモナドとは? ❖ (広義の)インターフェース ❖ 抽象的なモナドを実装できるプログラミング言語は少ない ❖ 主に高階型パラメータ、型クラスを用いて実装する ❖ (型クラスによって)既存のデータ型もモナドとして拡張できる ❖ 例: Scalaz や Cats の trait Monad[F[_]] { … }

Slide 21

Slide 21 text

具体的なモナドとは? ❖ (広義の)インターフェースに対するインスタンス、実装 ❖ 具体的なモナドインスタンスを実装できるプログラミング言語は多い ❖ 「モナドを知らなくても使える/使ってる」は主に具体的なモナドの方 ❖ 例: 標準ライブラリの Option[A], Either[E, A], List[A], etc…

Slide 22

Slide 22 text

【抽象的なモナドの実装】実装方法 ❖ 型クラスで抽象的なモナドを実装 ❖ Enrich my library パターンで拡張メソッドを提供する

Slide 23

Slide 23 text

【抽象的なモナドの実装】モナド型クラスの定義 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 }

Slide 24

Slide 24 text

【抽象的なモナドの実装】データ型の例 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() }

Slide 25

Slide 25 text

【抽象的なモナドの実装】インスタンスを定義 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() } }

Slide 26

Slide 26 text

【抽象的なモナドの実装】動作確認 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)

Slide 27

Slide 27 text

【抽象的なモナドの実装】モナド則を満たすか 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)) }

Slide 28

Slide 28 text

【抽象的なモナドの実装】モナド則を満たすか 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() // ... }

Slide 29

Slide 29 text

【抽象的なモナドの実装】モナド則を満たすか 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)) } // ... }

Slide 30

Slide 30 text

【抽象的なモナドの実装】モナド則を満たすか class MonadSpec extends Properties("Monad") { def law: Monad.Law[Maybe] = // ... // 結合律 property("associativity") = Prop.forAll { i: Int => law.associativity(odd(i), fizz, buzz) } }

Slide 31

Slide 31 text

【抽象的なモナドの実装】モナド則を満たすか 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

Slide 32

Slide 32 text

【抽象的なモナドの実装】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]

Slide 33

Slide 33 text

【抽象的なモナドの実装】for 式で合成するために ❖ 型クラスのインスタンスを定義しただけでは for 式で合成できない ❖ for 式が展開された後のシグネチャに合わせた map/flatMap が必要

Slide 34

Slide 34 text

【抽象的なモナドの実装】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) }

Slide 35

Slide 35 text

【抽象的なモナドの実装】for 式で合成するために // toMonadOps がスコープに定義されてる状態で for 式で合成ができる for { i <- Maybe.just(1) j <- Maybe.just(2) } yield i + j // res1: Maybe[Int] = Just(3)

Slide 36

Slide 36 text

【具体的なモナドの実装】実装方法 ❖ モナド則を満たすよう pure/flatMap を直接メソッドとして定義する

Slide 37

Slide 37 text

【具体的なモナドの実装】実装方法 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]

Slide 38

Slide 38 text

【具体的なモナドの実装】動作確認 // 特に暗黙の型変換をせずとも for 式で合成できる for { i <- Maybe.just(1) j <- Maybe.just(2) } yield i + j // res0: Maybe[Int] = Just(3)

Slide 39

Slide 39 text

モナドを活用するために

Slide 40

Slide 40 text

モナドって何?(再掲) ❖ モナド則を満たすように pure/flatMap を実装したもの

Slide 41

Slide 41 text

Scala における実用上のモナドの価値(再掲) ❖ データ型の「文脈」に依存しない汎用的な操作/合成を提供

Slide 42

Slide 42 text

Scala における「モナド」が示す二つの意味(再掲) ❖ 抽象的なモナド ❖ 具体的なモナド ※この区分と呼称は一般的なものではない。

Slide 43

Slide 43 text

具体的なモナドの例 ❖ 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

Slide 44

Slide 44 text

【具体的なモナド】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))) }

Slide 45

Slide 45 text

【具体的なモナド】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

Slide 46

Slide 46 text

【具体的なモナド】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))) }

Slide 47

Slide 47 text

【具体的なモナド】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 }

Slide 48

Slide 48 text

【具体的なモナド】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

Slide 49

Slide 49 text

【具体的なモナド】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))) }

Slide 50

Slide 50 text

【具体的なモナド】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!

Slide 51

Slide 51 text

【具体的なモナド】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!

Slide 52

Slide 52 text

抽象的なモナドの活用 ❖ MonadTransformer ❖ Effect Injection

Slide 53

Slide 53 text

【抽象的なモナド】MonadTransformer ❖ モナドの合成を考える ❖ def readInt: IO[Option[Int]] = IO { StdIn.readLine().toIntOption }

Slide 54

Slide 54 text

【抽象的なモナド】MonadTransformer val prog: IO[Option[Int]] = for { // IO[A] の合成 nOpt <- readInt mOpt <- readInt } yield for { // Option[A] の合成 n <- nOpt m <- mOpt } yield n + m

Slide 55

Slide 55 text

【抽象的なモナド】MonadTransformer scala> prog.unsafeRun() // 1 と 2 を入力 res6: Option[Int] = Some(3) scala> prog.unsafeRun() // 1 と a を入力 res7: Option[Int] = None

Slide 56

Slide 56 text

【抽象的なモナド】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] など

Slide 57

Slide 57 text

【抽象的なモナド】MonadTransformer object OptionT { def pure[F[_], A](a: A)(implicit M: Monad[F]): OptionT[F, A] = OptionT(M.pure(Some(a))) }

Slide 58

Slide 58 text

【抽象的なモナド】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) }) }

Slide 59

Slide 59 text

【抽象的なモナド】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) } }

Slide 60

Slide 60 text

【抽象的なモナド】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

Slide 61

Slide 61 text

【抽象的なモナド】MonadTransformer scala> prog.value.unsafeRun() // 1 と 2 をキーボードで入力 res3: Option[Int] = Some(3)

Slide 62

Slide 62 text

【抽象的なモナド】Effect Injection ❖ 返り値の型を F[_] に包み、(主に)実装クラスで Effect を差し替える ❖ F[_] に Monad の制約をかけることで for 式で書けることを約束 ❖ 巷で Tagless-final と呼ばれているパターン

Slide 63

Slide 63 text

【抽象的なモナド】Effect Injection // このタイミングでモナドの制約をかける必要は必ずしもない trait UserRepository[F[_]] { def resolveAll(ids: UserId*): F[Seq[User]] } trait MessageRepository[F[_]] { def resolveAll(ids: MessageId*): F[Seq[Message]] }

Slide 64

Slide 64 text

【抽象的なモナド】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 }

Slide 65

Slide 65 text

【抽象的なモナド】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]]]

Slide 66

Slide 66 text

【抽象的なモナド】Effect Injection - メリット ❖ Monad 型クラスのインスタンスが定義されていれば F[_] に当てはめられる => 実行する際の「文脈」をインターフェースから切りせる => あとで(実装するタイミングで)差し替えられる

Slide 67

Slide 67 text

【抽象的なモナド】Effect Injection - 注意点 ❖ あくまで F[_] に包まれたままでも(for 式などで)合成できることだけを保証 => 例外をハンドリングするか否かは F[_] の時点で保証できない

Slide 68

Slide 68 text

Conclusion

Slide 69

Slide 69 text

まとめ ❖ モナドとは、モナド則を満たすように pure/flatMap を実装したもの ❖ Scala においてデータ型の「文脈」に依存しない汎用的な操作/合成を提供 ❖ 抽象的なモナドと具体的なモナドの紹介と活用例を紹介した Scala におけるモナドは、知っておくと便利なツールの一つ

Slide 70

Slide 70 text

Appendix ➢ 補足、参考資料、ソースコードメモなど ○ https://gist.github.com/aoiroaoino/501a0fc0bedd4c631ed09dfa24d770a6 ○ https://github.com/aoiroaoino/fn