mp in scala

30
MP in Scala

Upload: kent-ohashi

Post on 16-Apr-2017

453 views

Category:

Software


5 download

TRANSCRIPT

Page 1: MP in Scala

MP in Scala

Page 2: MP in Scala

MP = Monadic Programming

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

Page 3: MP in Scala

自己紹介

Page 4: MP in Scala

(defprofile 大橋 賢人 [OHASHI Kent] :company 株式会社オプト テクノロジー開発2部 :github @lagenorhynque :twitter @lagenorhynque :languages [Python Haskell Clojure Scala English français Deutsch русский] :interests [プログラミング 語学 数学])

Page 5: MP in Scala

モナド(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 ...

Page 6: MP in Scala

モナドの具体例

例えば、 。Option

標準ライブラリでは型クラスとして実装されているわけではないが

Functor, Applicative, Monadに相当する機能を備えている。

Page 7: MP in Scala

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)

Page 8: MP in Scala

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)

Page 9: MP in Scala

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

Page 10: MP in Scala

同種のモナドを扱う場合

例えば、 Option を単独で使う場合

Page 11: MP in Scala

同種のモナドを扱う場合

(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 }

Page 12: MP in Scala

同種のモナドを扱う場合

(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" } }

Page 13: MP in Scala

同種のモナドを扱う場合

(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"

Page 14: MP in Scala

異種のモナドが混在する場合

例えば、 Option と を組み合わせてscalaz.\/

E \/ Option[A] として扱う必要がある場合

Page 15: MP in Scala

異種のモナドが混在する場合

(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) }

Page 16: MP in Scala

問題点

複数階層のモナドの分解・再構築が煩わしい構造に強く依存しているため変更に弱い

パターンマッチがネストして書きづらく読みづらい

Page 17: MP in Scala

異種のモナドが混在する場合

(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" } } }

Page 18: MP in Scala

問題点

構造を直接扱う必要はないが関数がネストして書きづらく読みづらい

Page 19: MP in Scala

異種のモナドが混在する場合

(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"

Page 20: MP in Scala

問題点

関数はネストしないがfor式が連鎖して書きづらく読みづらい

Page 22: MP in Scala

モナド変換子の生成と変換

// 型パラメータを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]

Page 23: MP in Scala

モナド変換子の導入

ここではモナド変換子 scalaz.OptionT を利用して

Option と scalaz.\/ を合成してみる。

Page 24: MP in Scala

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

Page 25: MP in Scala

Option[+A] と E \/ A をfor式1つでシンプルに扱える

モナド変換子への変換がやや冗長

Page 26: MP in Scala

さらにリファクタ

モナド変換子への変換を関数として抽出してみる。

Page 27: MP in Scala

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

Page 28: MP in Scala

ちなみに

Q: このtype aliasは何のためにあるのか?

type ErrorOrResult[+A] = Error \/ A

A: モナド変換子の型パラメータにカインド(kind)を合わせるため。

Page 29: MP in Scala

型 カインド

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]

Page 30: MP in Scala

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 CodeGist - lagenorhynque - monad-transformers