2011-10-12 4 views
72

私はスカラズ州のモナドの多くの例を見ていません。 this exampleがありますが、理解しにくく、スタックオーバーフローになっているようです。other questionです。Scalaz州のモナドの例

私が演奏したいくつかの例を掲載しますが、私は追加のものを歓迎します。また、誰かがinitmodifyputgetsのような理由で例を挙げることができるのであれば、それはすばらしいことになります。

編集:hereは、状態モナドの素晴らしい2時間のプレゼンテーションです。私が想定し

答えて

80

scalazのの7.0.xと、以下の輸入(scalaz 6.xののための回答履歴を見て):

import scalaz._ 
import Scalaz._ 

状態タイプはSがタイプですState[S, A]として定義されています状態のAは装飾される値のタイプです。

// Create a state computation incrementing the state and returning the "str" value 
val s = State[Int, String](i => (i + 1, "str")) 

初期値に状態計算を実行するには:状態は関数呼び出しに通すことができる

// start with state of 1, pass it to s 
s.eval(1) 
// returns result value "str" 

// same but only retrieve the state 
s.exec(1) 
// 2 

// get both state and value 
s(1) // or s.run(1) 
// (2, "str") 

状態値を作成するための基本的な構文はState[S, A]機能を利用します。 Function[A, B]の代わりにこれを行うには、Function[A, State[S, B]]]を定義します。

import java.util.Random 
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1)) 

... State機能を使用すると for/yield構文は、関数を構成するために使用することができます:ここでは

def TwoDice() = for { 
    r1 <- dice() 
    r2 <- dice() 
} yield (r1, r2) 

// start with a known seed 
TwoDice().eval(new Random(1L)) 
// resulting value is (Int, Int) = (4,5) 

は別の例です。 TwoDice()状態計算のリストを記入してください。

val list = List.fill(10)(TwoDice()) 
// List[scalaz.IndexedStateT[scalaz.Id.Id,Random,Random,(Int, Int)]] 

State[Random, List[(Int,Int)]]を取得するためにシーケンスを使用してください。タイプエイリアスを提供できます。

type StateRandom[x] = State[Random,x] 
val list2 = list.sequence[StateRandom, (Int,Int)] 
// list2: StateRandom[List[(Int, Int)]] = ... 
// run this computation starting with state new Random(1L) 
val tenDoubleThrows2 = list2.eval(new Random(1L)) 
// tenDoubleThrows2 : scalaz.Id.Id[List[(Int, Int)]] = 
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6)) 

それとも種類を推測なるsequenceUを使用することができます。

val list3 = list.sequenceU 
val tenDoubleThrows3 = list3.eval(new Random(1L)) 
// tenDoubleThrows3 : scalaz.Id.Id[List[(Int, Int)]] = 
// List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6)) 

State[Map[Int, Int], Int]とのもう一つの例は、上記のリストの和の周波数を計算します。 freqSumはスローとカウントの合計を計算します。

def freqSum(dice: (Int, Int)) = State[Map[Int,Int], Int]{ freq => 
    val s = dice._1 + dice._2 
    val tuple = s -> (freq.getOrElse(s, 0) + 1) 
    (freq + tuple, s) 
} 

tenDoubleThrowsfreqSumを適用するためにトラバースを使用します。 traversemap(freqSum).sequenceに相当します。

tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]()) 
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]] 

State[S, A]StateT[Id, S, A]の型の別名であるため、tenDoubleThrows2がIdとして入力されてしまうこと:タイプを推測するtraverseUを用いて

type StateFreq[x] = State[Map[Int,Int],x] 
// only get the state 
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]()) 
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]] 

以上簡潔。私はcopointを使ってListタイプに戻します。

簡潔に言えば、状態を使用する鍵は、状態と目的の実際の結果値を変更する関数を返すようになっているようです... 免責事項:私はstateをプロダクションコードで使用したことがなく、それを感じる。

@ziggystarコメントに関する追加情報

私はstateTを使用しようとあきらめたStateFreqStateRandomを組み合わせた計算を実行するために増強することができるならば他の誰かを示すことができるかもしれません。私が代わりに見つけた2つの状態の変圧器の構成は次のように組み合わせることができるということである:それはgが第1の状態変圧器の結果を取り、状態の変圧器を返送つのパラメータの関数であることを前提だ

def stateBicompose[S, T, A, B](
     f: State[S, A], 
     g: (A) => State[T, B]) = State[(S,T), B]{ case (s, t) => 
    val (newS, a) = f(s) 
    val (newT, b) = g(a) apply t 
    (newS, newT) -> b 
} 

。そして、次は動作します:

def diceAndFreqSum = stateBicompose(TwoDice, freqSum) 
type St2[x] = State[(Random, Map[Int,Int]), x] 
List.fill(10)(diceAndFreqSum).sequence[St2, Int].exec((new Random(1L), Map[Int,Int]())) 
+0

「状態」モナドは、状態の「状態変換」ではありませんか? 2番目の質問として、サイコロの回転と集計を1つのState Monadに組み合わせる方法がありますか?あなたは2つのモナドを与えられたらどうしますか? – ziggystar

+0

@ziggystar、技術的には 'StateFreq'と' StateRandom'はモナドです。 'S [x]はモナドである必要はないので、状態[S、x] 'はモナド変圧器ではないと私は考えます。結合するより良い方法のために、私はあまりにも不思議です。私は明らかに容易に利用可能なものは見当たりません。 'stateT'が役立つかもしれないが、私はまだそれを理解していない。 – huynhjl

+0

私は "モナド変圧器"ではなく "状態変圧器"と書いていました。 'State [S、x] 'オブジェクトは状態を保持するのではなく、後者を変換します。それは、名前があまり混乱しないように選ぶことができると思います。それはあなたの答えではなく、Scalazについてです。 – ziggystar

15

私はモナドトランスを介して2つの状態モナドを適用した例がありますSIGFPから興味深いブログ記事Grok Haskell Monad Transformersにつまずきました。ここにスカラズの翻訳があります。

最初の例State[Int, _]モナドを示しています

val test1 = for { 
    a <- init[Int] 
    _ <- modify[Int](_ + 1) 
    b <- init[Int] 
} yield (a, b) 

val go1 = test1 ! 0 
// (Int, Int) = (0,1) 

だから私はここにinitmodifyを使用する例があります。少しでも遊んだら、init[S]State[S,S]の値を生成するのが本当に便利だと判明しましたが、もう1つのことはfor comprehensionの中の状態にアクセスすることです。 modify[S]は、理解の中の状態を変換するのに便利な方法です。

  • a <- init[Int]:だから、上記の例のように読み取ることができ、Int状態で開始State[Int, _]モナドによってラップ値として設定し、a
  • _ <- modify[Int](_ + 1)にバインド:Int状態を増分
  • b <- init[Int]Int状態を取り、bにバインド(aと同じが、現在の状態がインクリメントされる)
  • State[Int, (Int, Int)]値USIを得ng aおよびb

for comprehensionシンタックスでは、A側のState[S, A]で作業するのは簡単です。init,modify,putおよびgetsState[S, A]S側で動作するツールを提供しています。非常に同じ説明test1として

val test2 = for { 
    a <- init[String] 
    _ <- modify[String](_ + "1") 
    b <- init[String] 
} yield (a, b) 

val go2 = test2 ! "0" 
// (String, String) = ("0","01") 

:ブログ記事内

第二の例はに変換されます。

第3の例は、もっとトリッキーですが、私がまだ発見していない何かがより簡単であることを願っています。そのコードで

type StateString[x] = State[String, x] 

val test3 = { 
    val stTrans = stateT[StateString, Int, String]{ i => 
    for { 
     _ <- init[String] 
     _ <- modify[String](_ + "1") 
     s <- init[String] 
    } yield (i+1, s) 
    } 
    val initT = stateT[StateString, Int, Int]{ s => (s,s).pure[StateString] } 
    for { 
    b <- stTrans 
    a <- initT 
    } yield (a, b) 
} 

val go3 = test3 ! 0 ! "0" 
// (Int, String) = (1,"01") 

stTransは、両方の状態("1"と増分とサフィックス)の形質転換ならびにString状態を引き出すの世話をします。 stateTは、任意のモナドに状態変換を追加することを可能にしますM。この場合、状態はインクリメントされるIntです。 stTrans ! 0を呼び出すと、M[String]となります。この例ではMStateStringなので、StateString[String]State[String, String]となります。

ここで難しいのは、Intの状態値をstTransから抜き出したいということです。これはinitTが対象です。 stTransでflatMapできるように状態にアクセスできるオブジェクトを作成するだけです。

編集:ここでは

// same as test3: 
val test31 = stateT[StateString, Int, (Int, String)]{ i => 
    val (_, a) = test1 ! i 
    for (t <- test2) yield (a, (a, t._2)) 
} 
10

は非常に小さな例です:私たちは本当に便利彼ら返されるタプルの_2要素で指名手配状態を記憶test1test2を再利用した場合、そのぎこちなさのすべてを回避することができ判明どのようにStateが使用できるかについて:

いくつかのゲームユニットが上司(ゲームユニットでもある)と戦っているところで、小さな「ゲーム」を定義しましょう。

case class GameUnit(health: Int) 
case class Game(score: Int, boss: GameUnit, party: List[GameUnit]) 


object Game { 
    val init = Game(0, GameUnit(100), List(GameUnit(20), GameUnit(10))) 
} 

我々はゲームの状態を追跡したい上のプレーはそうのは、Stateモナドの面で私たちの「行動」を定義してみましょう、次のとおりです。

彼はから10を失うそれでは、ハード上司を打つてみよう彼のhealth

def strike : State[Game, Unit] = modify[Game] { s => 
    s.copy(
    boss = s.boss.copy(health = s.boss.health - 10) 
) 
} 

そして、ボスは後退することができます!彼がパーティーで誰もが5 healthを失ったとき。

def fireBreath : State[Game, Unit] = modify[Game] { s => 
    val us = s.party 
    .map(u => u.copy(health = u.health - 5)) 
    .filter(_.health > 0) 

    s.copy(party = us) 
} 

今、私たちはplayこれらのアクションを構成することができます。実際の生活の中でもちろん

def play = for { 
    _ <- strike 
    _ <- fireBreath 
    _ <- fireBreath 
    _ <- strike 
} yield() 

プレーがよりダイナミックになりますが、それは私の小さな例のための十分な食料である:)

val res = play.exec(Game.init) 
println(res) 

>> Game(0,GameUnit(80),List(GameUnit(10))) 

我々は、ゲームの最終状態を確認するために、今、それを実行することができます

私たちは上司にほとんど負けず、ユニットの1つが死んでしまった、RIP。

ここでのポイントは、の組成です。 State(ちょうど関数S => (A, S))は、結果が生成されるアクションを定義したり、状態がどこから来ているかをあまり知らずに一部の状態を操作したりすることを可能にします。

A => State[S, B] 
B => State[S, C] 
------------------ 
A => State[S, C] 

など: Monad部分は、あなたのようにあなたの行動を構成することができる組成物が得られます。

P.S.一緒に

modifygetとして見ることができるとputgetputmodifyとの違いについては

def modify[S](f: S => S) : State[S, Unit] = for { 
    s <- get 
    _ <- put(f(s)) 
} yield() 

ほど単純

def modify[S](f: S => S) : State[S, Unit] = get[S].flatMap(s => put(f(s))) 

あなたは概念的getを使うmodify使用putを使用するか、単独で使用できます。