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. Scala における
    モナドって何だろう?
    2019/10/26 Scala関西Summit 2019
    Naoki Aoyama - @aoiroaoino

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. Introduction

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Scala におけるモナドの基本

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. 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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. モナドを活用するために

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. 【具体的なモナド】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] = [email protected]
    scala> io.unsafeRun()
    Hello!

    View Slide

  51. 【具体的なモナド】IO モナド
    scala> val app = for {
    | _ <- println("Message:")
    | s <- readLine
    | _ <- println(s"Your message is $s")
    | } yield s
    app: fn.data.IO[String] = [email protected]
    scala> app.unsafeRun()
    Message:
    Your message is Hello!
    res15: String = Hello!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. 【抽象的なモナド】MonadTransformer
    scala> val prog = for {
    | i <- OptionT(readInt)
    | j <- OptionT(readInt)
    | } yield i + j
    prog: fn.data.OptionT[fn.data.IO,Int] = OptionT([email protected])
    scala> prog.value
    res2: fn.data.IO[Option[Int]] = [email protected]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  68. Conclusion

    View Slide

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

    View Slide

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

    View Slide