ScalazのStateモナドを使う機会があったのでメモ。
Stateモナドとは?
- 状態を含む計算を簡潔に記述する方法
- しかもモナド
たとえばこのような処理があったとする。
def foo(arg: Int): Int = { val s1 = arg + 10 val s2 = s1 * 2 val s3 = s2 - 4 s3 }
何度も変数を新たに作成しているので効率が悪い。
かといってvarを使うのも関数型言語の恥だし、バグの温床になるので避けたい。
def foo2(arg: Int): Int = { var temp = arg + 10 temp = temp * 2 temp = temp - 4 temp }
それなら処理を一行にまとめれば済む話だが、処理に影響するコードが挟まれてたりすると厄介だ。
たとえば、初期化処理とか、コンストラクタのような。
def foo3(arg: Int): Int = { val s1 = arg + 10 // some preparing val s2 = functionNeedsPrepare(s1) // some preparing val instance = new someClass(s1) val s3 = instance.hoge(s2) s3 }
ここでStateモナドの出番だ。
Stateモナドは変数を局在化し、外部への影響を無くしてくれる。
『状態を受け取り、処理の結果と新たな状態を返す』処理が抽象化される。
import scalaz._ import Scalaz._ def foo4 = for{ p <- init[Int] _ <- modify(_ + 10) _ <- modify(prepareStatement(p)) result <- gets(hoge) } yields result val prepareStatement = state[Int, Int](x => someprepare(); val result = functionNeedsPrepare(x) (result, result) ) val hoge = (p: Int) => state[Int, String]( x => val instance = new someClass(x) val result = instance.hoge() // => String (x+p, result) )
おおまかな使い方は、
- scalazをimportする
- for-comprehensionとstateモナドの関数で処理を連結する
- yieldで値を取得する
といったところだ。
stateモナドの便利関数
// 計算の途上で状態を初期化する。Sが破棄されているのがわかる def constantState[S, A](a: A, s: => S): State[S, A] = State((_: S) => (s, a)) // 『状態から(状態, 値)のペアを返す関数』(S => (S, A))を渡すとStateモナド化される。 // 値と状態の両方を更新する。 def state[S, A](a: A): State[S, A] = State((_ : S, a)) // 初期状態を作成する。値は状態と同じとなる def init[S]: State[S, S] = State(s => (s, s)) // 現在の状態を値として取得する。initと同じ処理。 def get[S]: State[S, S] = init // 処理をして得られた結果を値として返す。状態への干渉は行わない。 def gets[S, T](f: S => T): State[S, T] = State(s => (s, f(s))) // 状態を上書きする。 def put[S](s: S): State[S, Unit] = State(_ => (s, ())) // 状態に関数を適用し、状態を更新する。値は返さない。 def modify[S](f: S => S): State[S, Unit] = State(s => { val r = f(s); (r, ()) })
引用元: 独習 Scalaz: 7日目