281 Views
May 27, 16
スライド概要
Scalaでモナディックプログラミング!
1. モナドとは何か
2. モナドの基本的な扱い方
3. モナド変換子の使い方
cf. Haskell版: https://www.docswell.com/s/lagenorhynque/KQ8RPR-mp-in-haskell
「楽しく楽にcoolにsmartに」を理想とするprogrammer/philosopher/liberalist/realist。 好きな言語はClojure, Haskell, Python, English, français, русский。 読書、プログラミング、語学、法学、数学が大好き! イルカと海も大好き🐬
MP in Scala
MP = Monadic Programming 1. 2. 3. モナドとは何か モナドの基本的な扱い方 モナド変換子の使い方
自己紹介
(defprofile [OHASHI Kent] :company ー :github @lagenorhynque :twitter @lagenorhynque :languages [Python Haskell Clojure Scala English français Deutsch русский] :interests [ ]) 大橋 賢人 株式会社オプト テクノロジ 開発2部 プログラミング 語学 数学
モナド(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
...
モナドの具体例 例えば、 Option。 標準ライブラリでは型クラスとして実装されているわけではないが Functor, Applicative, Monadに相当する機能を備えている。
の map
Functor
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)
式による実装
による実装
の ap/ <*>
Applicative
F[A] => F[A => B] => F[B]
にあたる。
scalaz.Apply.ap, scalaz.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)
式による実装
による実装
の flatMap/ bind/ >>=
Monad
F[A] => (A => F[B]) => F[B]
にあたる。
scalaz.Bind.bind, scalaz.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
式による実装
同種のモナドを扱う場合 例えば、 Optionを単独で使う場合
同種のモナドを扱う場合 (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 }
同種のモナドを扱う場合
(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"
}
}
同種のモナドを扱う場合
式
(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"
異種のモナドが混在する場合 例えば、 Optionと scalaz.\/を組み合わせて E \/ Option[A]として扱う必要がある場合
異種のモナドが混在する場合
(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)
}
問題点 複数階層のモナドの分解・再構築が煩わしい 構造に強く依存しているため変更に弱い パターンマッチがネストして書きづらく読みづらい
異種のモナドが混在する場合
(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"
}
}
}
問題点 構造を直接扱う必要はないが関数がネストして書きづらく読みづらい
異種のモナドが混在する場合
式
(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"
問題点 関数はネストしないがfor式が連鎖して書きづらく読みづらい
モナド変換子(monad transformer)とは 一言で言えば、あるモナドに別のモナドを上乗せ(合成)したモナド。 ネストしたモナドをネストしていないかのように扱えるようになる。 e.g. scalaz.OptionT, scalaz.EitherT, scalaz.ListT
モナド変換子の生成と変換 型パラメ タを 個とる型F(モナド) // ー 1 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]
モナド変換子の導入 ここではモナド変換子 scalaz.OptionTを利用して Optionと scalaz.\/を合成してみる。
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
と E \/ Aをfor式1つでシンプルに扱える モナド変換子への変換がやや冗長 Option[+A]
さらにリファクタ モナド変換子への変換を関数として抽出してみる。
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
ちなみに Q: このtype aliasは何のためにあるのか? type ErrorOrResult[+A] = Error \/ A A: モナド変換子の型パラメータにカインド(kind)を合わせるため。
型 の カインド 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] 方法 コンパイラプラグインKind Projectorを利用する // 3: OptionT[Error \/ +?, A]
Further Reading のシンプルな定式化 - Qiita ScalaでFutureとEitherを組み合わせたときに綺麗に書く方法 - scala とか・・・ Functor, Applicative, Monad Scalaz Monad Transformers - Underscore 独習 Scalaz — モナド変換子 Easy Monad Haskell モナド変換子 超入門 - Qiita All About Monads - Sampou.Org Source Code Gist - lagenorhynque - monad-transformers