web-dev-qa-db-ja.com

Scalaz状態モナドの例

私は、scalaz状態モナドの多くの例を見ていない。 この例 がありますが、理解するのが難しく、たった1つしかありません 他の質問 スタックオーバーフローのようです。

プレイしたいくつかの例を投稿しますが、追加の例を歓迎します。また、誰かがinitmodifyput、およびgetsが使用される理由の例を提供できれば、それは素晴らしいことです。

編集: ここ は、状態モナドに関する素晴らしい2時間のプレゼンテーションです。

76
huynhjl

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

トラバースを使用して、freqSumtenDoubleThrowsを適用します。 traversemap(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]()))
_
83
huynhjl

私は興味深いブログ投稿につまずきました 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)
_

initmodifyの使用例があります。少し遊んだ後、_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と同じですが、状態はインクリメントされます)
  • abを使用してState[Int, (Int, Int)]値を生成します。

For内包構文により、_State[S, A]_のA側で作業するのは簡単です。 initmodifyputおよび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]_になります。この例では、MStateStringであるため、_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))
}
_
15
huynhjl

以下は、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。getputmodifyの違いについて:

modifyは、getputとして一緒に見ることができます:

_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を使用する場合、概念的にはgetputを使用しますが、単独で使用することもできます。

13
Alexey Raga