私は、scalaz状態モナドの多くの例を見ていない。 この例 がありますが、理解するのが難しく、たった1つしかありません 他の質問 スタックオーバーフローのようです。
プレイしたいくつかの例を投稿しますが、追加の例を歓迎します。また、誰かがinit
、modify
、put
、およびgets
が使用される理由の例を提供できれば、それは素晴らしいことです。
編集: ここ は、状態モナドに関する素晴らしい2時間のプレゼンテーションです。
scalaz 7.0.xと次のインポート(scalaz 6.x):
_import scalaz._
import Scalaz._
_
状態タイプは_State[S, A]
_として定義されます。ここで、S
は状態のタイプであり、A
は装飾される値のタイプです。状態値を作成する基本的な構文は、_State[S, 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")
_
状態は、関数呼び出しを介してスレッド化できます。 _Function[A, B]
_の代わりにこれを行うには、_Function[A, State[S, B]]]
_を定義します。 State
関数を使用...
_import Java.util.Random
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))
_
次に、_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)
}
_
トラバースを使用して、freqSum
にtenDoubleThrows
を適用します。 traverse
はmap(freqSum).sequence
と同等です。
_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]]
_
または、より簡潔にtraverseU
を使用して型を推測します。
_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
として入力されることに注意してください。 copoint
を使用して、List
型に戻します。
要するに、状態を使用する鍵は、状態を変更する関数を返す関数と必要な実際の結果値を取得することです...免責事項:state
を使用したことはありません実動コードでは、ただそれを感じようとしています。
@ ziggystarコメントに関する追加情報
stateT
またはStateFreq
を組み合わせて計算を実行できるかどうかは、StateRandom
を使用してみることをあきらめた可能性があります。私が代わりに見つけたのは、2つの状態変換器の構成を次のように組み合わせることができるということです。
_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
}
_
g
は、最初の状態変換器の結果を取得して状態変換器を返す1つのパラメーター関数であることに基づいています。その後、次のように動作します:
_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]()))
_
私は興味深いブログ投稿につまずきました Grok Haskell Monad Transformers sigfpからモナド変換器を通して2つの状態モナドを適用する例があります。これがscalazの翻訳です。
最初の例は_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)
_
init
とmodify
の使用例があります。少し遊んだ後、_init[S]
_は_State[S,S]
_値を生成するのに非常に便利であることが判明しましたが、それが許可するもう1つのことは、理解のために内部の状態にアクセスすることです。 _modify[S]
_は、内部の状態を理解のために変換する便利な方法です。したがって、上記の例は次のように読むことができます。
a <- init[Int]
_:Int
状態で開始し、_State[Int, _]
_モナドでラップされた値として設定し、a
にバインドします_ <- modify[Int](_ + 1)
:Int
状態をインクリメントしますb <- init[Int]
_:Int
状態を取得し、それをb
にバインドします(a
と同じですが、状態はインクリメントされます)a
とb
を使用してState[Int, (Int, Int)]
値を生成します。For内包構文により、_State[S, A]
_のA
側で作業するのは簡単です。 init
、modify
、put
およびgets
は、_State[S, A]
_のS
側で動作するツールを提供します。
ブログ投稿の2番目の例は次のように翻訳されます:
_val test2 = for {
a <- init[String]
_ <- modify[String](_ + "1")
b <- init[String]
} yield (a, b)
val go2 = test2 ! "0"
// (String, String) = ("0","01")
_
_test1
_とほぼ同じ説明。
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]
_になります。この例では、M
はStateString
であるため、_StateString[String]
_である_State[String, String]
_になります。
ここで注意が必要なのは、Int
からstTrans
状態値を引き出すことです。これがinitT
の目的です。 stTrans
でflatMapできる方法で状態へのアクセスを提供するオブジェクトを作成するだけです。
編集:返されたタプルの_test1
_要素に必要な状態を便利に保存する_test2
_と__2
_を本当に再利用すれば、その厄介さをすべて回避できることがわかります。
_// same as test3:
val test31 = stateT[StateString, Int, (Int, String)]{ i =>
val (_, a) = test1 ! i
for (t <- test2) yield (a, (a, t._2))
}
_
以下は、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)))
}
_
プレイが始まると、ゲームの状態を追跡したいので、状態モナドの観点から「アクション」を定義しましょう。
ボスを激しく叩いて、health
から10を失います。
_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)
}
_
これでcomposeこれらのアクションを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が死にました。
ここでのポイントはcompositionです。 State
(単なる関数S => (A, S)
)を使用すると、結果を生成するアクションを定義し、状態の発生元をあまり知らなくても状態を操作できます。 Monad
部分は、アクションを構成できるように構成を提供します。
_ A => State[S, B]
B => State[S, C]
------------------
A => State[S, C]
_
等々。
P.S。get
、put
、modify
の違いについて:
modify
は、get
とput
として一緒に見ることができます:
_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)))
_
したがって、modify
を使用する場合、概念的にはget
とput
を使用しますが、単独で使用することもできます。