Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

MP in Scala

598 views

Published on

Scalaでモナディックプログラミング!

1. モナドとは何か
2. モナドの基本的な扱い方
3. モナド変換子の使い方

Published in: Software
  • Be the first to comment

MP in Scala

  1. 1. MP in Scala
  2. 2. MP = Monadic Programming 1. モナドとは何か 2. モナドの基本的な扱い方 3. モナド変換子の使い方
  3. 3. 自己紹介
  4. 4. (defprofile 大橋 賢人 [OHASHI Kent] :company 株式会社オプト テクノロジー開発2部 :github @lagenorhynque :twitter @lagenorhynque :languages [Python Haskell Clojure Scala English français Deutsch русский] :interests [プログラミング 語学 数学])
  5. 5. モナド(Monad)とは 一言で言えば、 flatMapできる型クラス(type class)。 F[A] => (A => F[B]) => F[B] 型クラスFunctor, Applicativeの上位互換。 // scalaz.Monad trait Monad[F[_]] extends Applicative[F] with Bind[F] { abstract def bind[A, B](fa: F[A])(f: (A) ⇒ F[B]): F[B] abstract def point[A](a: ⇒ A): F[A] ... } -- Haskell class Applicative m => Monad m where (>>=) :: forall a b. m a -> (a -> m b) -> m b return :: a -> m a ...
  6. 6. モナドの具体例 例えば、 。Option 標準ライブラリでは型クラスとして実装されているわけではないが Functor, Applicative, Monadに相当する機能を備えている。
  7. 7. Functorの map F[A] => (A => B) => F[B] にあたる。scalaz.Functor.map val n: Option[Int] = Some(1) val f: Int => String = x => x.toString // Option#map n.map(f) // Option#flatMap による実装 n.flatMap { a => Some(f(a)) } // for式による実装 for { a <- n } yield f(a)
  8. 8. Applicativeの ap / <*> F[A] => F[A => B] => F[B] , にあたる。scalaz.Apply.apscalaz.syntax.ApplyOps.<*> val n: Option[Int] = Some(1) val f: Option[Int => String] = Some(x => x.toString) // Option#flatMap, Option#map による実装 n.flatMap { a => f.map { g => g(a) } } // for式による実装 for { a <- n g <- f } yield g(a)
  9. 9. Monadの flatMap / bind / >>= F[A] => (A => F[B]) => F[B] , にあたる。scalaz.Bind.bindscalaz.syntax.BindOps.>>= val n: Option[Int] = Some(1) val f: Int => Option[String] = x => Some(x.toString) // Option#flatMap n.flatMap(f) // for式による実装 for { a <- n b <- f(a) } yield b
  10. 10. 同種のモナドを扱う場合 例えば、 Optionを単独で使う場合
  11. 11. 同種のモナドを扱う場合 (1) パターンマッチ 構造に注目して分解(unapply, destructure)する。 case class User(firstName: Option[String], lastName: Option[String]) def userName(user: User): Option[String] = user match { case User(Some(first), Some(last)) => Some(s"$first $last") case _ => None }
  12. 12. 同種のモナドを扱う場合 (2) 高階関数 高階関数map, flatMap, etc.を組み合わせる。 case class User(firstName: Option[String], lastName: Option[String]) def userName(user: User): Option[String] = user.firstName.flatMap { first => user.lastName.map { last => s"$first $last" } }
  13. 13. 同種のモナドを扱う場合 (3) for式 モナドのためのシンタックスシュガーを活用する。 case class User(firstName: Option[String], lastName: Option[String]) def userName(user: User): Option[String] = for { first <- user.firstName last <- user.lastName } yield s"$first $last"
  14. 14. 異種のモナドが混在する場合 例えば、 Optionと を組み合わせてscalaz./ E / Option[A]として扱う必要がある場合
  15. 15. 異種のモナドが混在する場合 (1) パターンマッチ import scalaz._, Scalaz._ case class User(id: Int, firstName: Option[String], lastName: Option[String]) def userRole(id: Int): Error / Role = ??? def userInfo(user: User): Error / Option[String] = userRole(user.id) match { case /-(role) => user match { case User(_, Some(first), Some(last)) => /-(Some(s"$first $last: $role")) case _ => /-(None) } case -/(error) => -/(error) }
  16. 16. 問題点 複数階層のモナドの分解・再構築が煩わしい 構造に強く依存しているため変更に弱い パターンマッチがネストして書きづらく読みづらい
  17. 17. 異種のモナドが混在する場合 (2) 高階関数 import scalaz._, Scalaz._ case class User(id: Int, firstName: Option[String], lastName: Option[String]) def userRole(id: Int): Error / Role = ??? def userInfo(user: User): Error / Option[String] = userRole(user.id).map { role => user.firstName.flatMap { first => user.lastName.map { last => s"$first $last: $role" } } }
  18. 18. 問題点 構造を直接扱う必要はないが関数がネストして書きづらく読みづらい
  19. 19. 異種のモナドが混在する場合 (3) for式 import scalaz._, Scalaz._ case class User(id: Int, firstName: Option[String], lastName: Option[String]) def userRole(id: Int): Error / Role = ??? def userInfo(user: User): Error / Option[String] = for { role <- userRole(user.id) } yield for { first <- user.firstName last <- user.lastName } yield s"$first $last: $role"
  20. 20. 問題点 関数はネストしないがfor式が連鎖して書きづらく読みづらい
  21. 21. モナド変換子(monad transformer)とは 一言で言えば、あるモナドに別のモナドを上乗せ(合成)したモナド。 ネストしたモナドをネストしていないかのように扱えるようになる。 e.g. , ,scalaz.OptionTscalaz.EitherTscalaz.ListT
  22. 22. モナド変換子の生成と変換 // 型パラメータを1個とる型F(モナド) type F[A] = ??? // FとOptionでネストしたモナド val fOptionA: F[Option[A]] = ??? // OptionとFを合成したOptionT val optionTFA: OptionT[F, A] = ??? // Option val optionA: Option[A] = ??? // F val fA: F[A] = ??? // F[Option[A]] → OptionT[F, A] OptionT.optionT(fOptionA) // OptionT[F, A] → F[Option[A]] optionTFA.run // Option[A] → F[Option[A]] → OptionT[F, A] OptionT.optionT(optionA.point[F]) // F[A] → OptionT[F, A] fA.liftM[OptionT]
  23. 23. モナド変換子の導入 ここではモナド変換子scalaz.OptionTを利用して Optionとscalaz./を合成してみる。
  24. 24. import scalaz._, Scalaz._ case class User(id: Int, firstName: Option[String], lastName: Option[String]) def userRole(id: Int): Error / Role = ??? type ErrorOrResult[+A] = Error / A def userInfo(user: User): Error / Option[String] = (for { role <- userRole(user.id).liftM[OptionT] first <- OptionT.optionT(user.firstName.point[ErrorOrResult]) last <- OptionT.optionT(user.lastName.point[ErrorOrResult]) } yield s"$first $last: $role").run
  25. 25. Option[+A]とE / Aをfor式1つでシンプルに扱える モナド変換子への変換がやや冗長
  26. 26. さらにリファクタ モナド変換子への変換を関数として抽出してみる。
  27. 27. import scalaz._, Scalaz._ case class User(id: Int, firstName: Option[String], lastName: Option[String]) def userRole(id: Int): Error / Role = ??? type ErrorOrResult[+A] = Error / A def fromOption[A](a: Option[A]): OptionT[ErrorOrResult, A] = OptionT.optionT(a.point[ErrorOrResult]) def fromEither[A](a: ErrorOrResult[A]): OptionT[ErrorOrResult, A] = a.liftM[OptionT] def userInfo(user: User): Error / Option[String] = (for { role <- userRole(user.id) ▹ fromEither first <- user.firstName ▹ fromOption last <- user.lastName ▹ fromOption } yield s"$first $last: $role").run
  28. 28. ちなみに Q: このtype aliasは何のためにあるのか? type ErrorOrResult[+A] = Error / A A: モナド変換子の型パラメータにカインド(kind)を合わせるため。
  29. 29. 型 カインド OptionT[F[_], A]のF[_] * -> * /[+A, +B] * -> * -> * // 方法1: type aliasする type ErrorOrResult[+A] = Error / A OptionT[ErrorOrResult, A] // 方法2: インラインでtype aliasする(type lambda) OptionT[({type λ[+α] = Error / α})#λ, A] // 方法3: コンパイラプラグインKind Projectorを利用する OptionT[Error / +?, A]
  30. 30. Further Reading Functor, Applicative, Monadのシンプルな定式化- Qiita ScalaでFutureとEitherを組み合わせたときに綺麗に書く方法- scala とか・・・ Scalaz Monad Transformers - Underscore 独習Scalaz — モナド変換子 Easy Monad Haskell モナド変換子超入門- Qiita All About Monads - Sampou.Org Source Code Gist - lagenorhynque - monad-transformers

×