我还没有看到很多scalaz state monad的例子。有这个例子,但很难理解,而且似乎只有另一个关于堆栈溢出的问题。
我将发布一些我玩过的例子,但我欢迎更多的例子。另外,如果有人可以提供有关为什么使用 , 和 的示例init
,那就modify
太好了。put
gets
编辑:这是关于状态单子的精彩 2 小时演示。
我还没有看到很多scalaz state monad的例子。有这个例子,但很难理解,而且似乎只有另一个关于堆栈溢出的问题。
我将发布一些我玩过的例子,但我欢迎更多的例子。另外,如果有人可以提供有关为什么使用 , 和 的示例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
which 来推断类型:
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 应用freqSum
over 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
可以增强以执行组合计算。相反,我发现两个状态转换器的组成可以这样组合:
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]()))
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
stateb <- init[Int]
: take the Int
state and bind it to b
(same as for a
but now the state is incremented)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))
}
这是一个关于如何使用的非常小的示例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
可以看作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
and put
,或者您可以单独使用它们。