scalaz 状態モナドの例はあまり見たことがありません。この例がありますが、理解するのが難しく、スタック オーバーフローに関する他の質問が 1 つだけあるようです。
私が遊んだいくつかの例を投稿するつもりですが、追加のものを歓迎します。init
また、なぜ、modify
、put
およびがそのために使用されているかについての例を誰かが提供できれば、gets
それは素晴らしいことです。
編集:これは状態モナドに関する素晴らしい 2 時間のプレゼンテーションです。
scalaz 状態モナドの例はあまり見たことがありません。この例がありますが、理解するのが難しく、スタック オーバーフローに関する他の質問が 1 つだけあるようです。
私が遊んだいくつかの例を投稿するつもりですが、追加のものを歓迎します。init
また、なぜ、modify
、put
およびがそのために使用されているかについての例を誰かが提供できれば、gets
それは素晴らしいことです。
編集:これは状態モナドに関する素晴らしい 2 時間のプレゼンテーションです。
興味深いブログ投稿Grok Haskell Monad Transformersを sigfp から見つけました。これには、モナド トランスフォーマーを介して 2 つの状態モナドを適用する例が含まれています。これがスカラス翻訳です。
最初の例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
が、状態がインクリメントされます)State[Int, (Int, Int)]
を使用して値を生成します。a
b
for 内包表記の構文によりA
、State[S, A]
. init
、modify
、put
およびの側面でgets
作業するためのいくつかのツールを提供します。S
State[S, A]
ブログ投稿の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
、両方の状態の変換 (インクリメントと接尾辞) を処理します。任意のモナドに状態変換を追加することができます。この場合、状態はインクリメントされます。呼び出した場合、 になります。この例では isであるため、最終的にはwhich is になります。 "1"
String
stateT
M
Int
stTrans ! 0
M[String]
M
StateString
StateString[String]
State[String, String]
ここで注意が必要なのは、Int
から状態値を取り出したいということですstTrans
。これがinitT
目的です。で flatMap できる方法で状態へのアクセスを提供するオブジェクトを作成するだけstTrans
です。
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)))
}
プレイ中はゲームの状態を追跡したいので、状態モナドの観点から「アクション」を定義しましょう。
ボスを激しく攻撃して、彼が から 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)
) を使用すると、結果を生成するアクションを定義したり、状態がどこから来ているかをあまり知らなくても状態を操作したりできます。パーツはMonad
構成を提供するため、アクションを構成できます。
A => State[S, B]
B => State[S, C]
------------------
A => State[S, C]
等々。
PSとの違いについて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
か、それらを単独で使用することができます。