2012年6月27日水曜日

Stateもなど

Stateモナドの有用性を考えるために書いてみます。

case class Range(node: Node, offset: Int)
def getRange = IO(Range(<p>{ Random.nextString(5) }</p>, Random.nextInt(5)))
def nextNode(node: Node) = IO(<p>{ Random.nextString(5) }</p>)
def prevNode(node: Node) = IO(<p>{ Random.nextString(5) }</p>)
def move(range: Range) = IO(())
view raw dom.scala hosted with ❤ by GitHub

Rangeオブジェクトはカーソルが指すNodeとoffsetを持っています。
getRangeは現在のRangeを返すものとします。
moveはそのRangeにカーソル移動するものとします。
nextNodeとprevNodeはそれぞれ次のNodeと前のNodeを返します。

getRange,nextNode,prevNode,moveは擬似的なものなので、定義は気にしないで下さい。
この4つの関数は参照透過とかそんなわけないのでIOを返します。

これらに対して、4方向にカーソルを動かす関数を定義します。

def moveLeft = for {
r <- getRange
} yield move(r.copy(offset = r.offset.pred))
def moveRight = for {
r <- getRange
} yield move(r.copy(offset = r.offset.succ))
def moveUp = for {
r <- getRange
n <- prevNode(r.node)
} yield move(r.copy(node = n))
def moveDown = for {
r <- getRange
n <- nextNode(r.node)
} yield move(r.copy(node = n))
view raw move1.scala hosted with ❤ by GitHub

IOはモナドなのでforが使えます。
predとsuccはscalaz.Enumのシンタックスでデクリメントとインクリメントのようなものです。

実際には移動するときに、offsetなどを調べるものですが、OptionT[IO, Unit]などと複雑になるので省略します。
これらの関数を組み合わせてみます。

def movemovemove = moveLeft >> moveRight >> moveUp >> moveDown
view raw comp1.scala hosted with ❤ by GitHub

さて、これらの関数、ちょっと冗長ですね。
いちいちmoveを実行しているところもいただけません。

これらをStateを使って書きなおしてみます。

def moveLeft = State[Range, Range](r => r.copy(offset = r.offset.pred).squared)
def moveRight = State[Range, Range](r => r.copy(offset = r.offset.succ).squared)
def moveUp = StateT[IO, Range, Range](r => prevNode(r.node) map (n => r.copy(node = n).squared))
def moveDown = StateT[IO, Range, Range](r => nextNode(r.node) map (n => r.copy(node = n).squared))
view raw move2.scala hosted with ❤ by GitHub

squaredは値をペアにして返します。

同じように、合成したStateとそれを使って移動する関数を定義します。

def movemovemove = for {
_ <- moveLeft.lift[IO]
_ <- moveRight.lift[IO]
_ <- moveUp
r <- moveDown
} yield r
def run = getRange >>= movemovemove.eval >>= move
view raw comp2.scala hosted with ❤ by GitHub

getRangeとmoveの呼び出しは1回で済むようになりました。
movemovemoveというStateを使い回すことも出来ます。

さらに、Stateモナドであることを活かして、次のようなStateを定義することが出来ます。

def moveDownStart = for {
r <- moveDown
rr <- List.fill(r.offset)(moveLeft).foldRight(State.init[Range])(_ >> _).lift[IO]
} yield rr
view raw state.scala hosted with ❤ by GitHub

移動した直後の状態を扱えるのがStateモナドの利点ですね。



最近は大学生がなかなか忙しいです。
俺、定期試験が終わったらScalaz勉強会開くんだ・・・・・

※追記

Intを返すようなStateになってしまうけど、Lensを使ってみても面白いかも。

def offset = Lens.lensg[Range, Int](r => o => r.copy(offset = o), _.offset)
def movemovemove = for {
_ <- (offset -= 1).lift[IO]
_ <- (offset += 1).lift[IO]
_ <- moveUp
r <- moveDown
} yield r
view raw lens.scala hosted with ❤ by GitHub

0 件のコメント:

コメントを投稿