77

scalaz 状態モナドの例はあまり見たことがありません。この例がありますが、理解するのが難しく、スタック オーバーフローに関する他の質問が 1 つだけあるようです。

私が遊んだいくつかの例を投稿するつもりですが、追加のものを歓迎します。initまた、なぜ、modifyputおよびがそのために使用されているかについての例を誰かが提供できれば、getsそれは素晴らしいことです。

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

4

3 に答える 3

15

興味深いブログ投稿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)]を使用して値を生成します。ab

for 内包表記の構文によりAState[S, A]. initmodifyputおよびの側面でgets作業するためのいくつかのツールを提供します。SState[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"StringstateTMIntstTrans ! 0M[String]MStateStringStateString[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))
}
于 2011-10-16T02:52:49.740 に答える
14

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は、次のとおりです。putmodify

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か、それらを単独で使用することができます。

于 2015-12-22T04:47:50.647 に答える