77

我还没有看到很多scalaz state monad的例子。有这个例子,但很难理解,而且似乎只有另一个关于堆栈溢出的问题。

我将发布一些我玩过的例子,但我欢迎更多的例子。另外,如果有人可以提供有关为什么使用 , 和 的示例init,那就modify太好了。putgets

编辑:是关于状态单子的精彩 2 小时演示。

4

3 回答 3

83

我假设,scalaz 7.0.x和以下导入(查看scalaz 6.x的答案历史记录):

import scalaz._
import Scalaz._

状态类型被定义为State[S, A]状态的类型SA被修饰的值的类型。创建状态值的基本语法使用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))

或者我们可以使用sequenceUwhich 来推断类型:

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

现在使用 traverse 应用freqSumover tenDoubleThrowstraverse相当于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可能是其他人可以显示是否StateFreqStateRandom可以增强以执行组合计算。相反,我发现两个状态转换器的组成可以这样组合:

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是作为一个参数函数,获取第一个状态转换器的结果并返回一个状态转换器。然后以下将起作用:

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]()))
于 2011-10-12T03:15:55.563 回答
15

I stumbled on an interesting blog post Grok Haskell Monad Transformers from sigfp that has an example of applying two state monads through a monad transformer. Here is a scalaz translation.

The first example shows a State[Int, _] monad:

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

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

So I have here an example of using init and modify. After playing with it a bit, init[S] turns out to be really convenient to generate a State[S,S] value, but the other thing it allows is to access the state inside the for comprehension. modify[S] is a convenient way to transform the state inside the for comprehension. So the example above can be read as:

  • a <- init[Int]: start with an Int state, set it as the value wrapped by the State[Int, _] monad and bind it to a
  • _ <- modify[Int](_ + 1): increment the Int state
  • b <- init[Int]: take the Int state and bind it to b (same as for a but now the state is incremented)
  • yield a State[Int, (Int, Int)] value using a and b.

The for comprehension syntax already makes it trivial to work on the A side in State[S, A]. init, modify, put and gets provide some tools to work on the S side in State[S, A].

The second example in the blog post translates to:

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

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

Very much the same explanation as test1.

The third example is more tricky and I hope there is something simpler that I have yet to discover.

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")

In that code, stTrans takes care of the transformation of both states (increment and suffix with "1") as well as pulling out the String state. stateT allows us to add state transformation on an arbitrary monad M. In this case the state is an Int that is incremented. If we called stTrans ! 0 we would end up with M[String]. In our example, M is StateString, so we'll end up with StateString[String] which is State[String, String].

The tricky part here is that we want to pull out the Int state value out from stTrans. This is what initT is for. It just creates an object that gives access to the state in a way we can flatMap with stTrans.

Edit: Turns out all of that awkwardness can be avoided if we truly reused test1 and test2 which conveniently store the wanted states in the _2 element of their returned tuples:

// 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)))

所以我们几乎没有击中boss,其中一个单位已经死了,RIP。

这里的重点是构图State(这只是一个函数S => (A, S))允许您定义产生结果的操作并在不知道状态来自何处的情况下操纵某些状态。该Monad部分为您提供组合,以便您的操作可以组合:

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

等等。

PS至于get,put和之间的区别modify

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在概念上使用getand put,或者您可以单独使用它们。

于 2015-12-22T04:47:50.647 回答