Lambdaカクテル

ソフトウェア開発者です.玉石混淆です.

State monad in Scala via Scalaz

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

おおまかな使い方は、

  1. scalazをimportする
  2. for-comprehensionとstateモナドの関数で処理を連結する
  3. 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日目