我还没有看到斯卡拉状态单子的许多例子。有this example,但它很难理解,似乎只有一个other question堆栈溢出。斯卡拉斯状态单子例子
我打算发表一些我玩过的例子,但我会欢迎更多的例子。此外,如果有人可以提供例子,为什么init
,modify
, put
和gets
用于这将是伟大的。
编辑:here是一个很棒的2个小时的国家单体演示。
我还没有看到斯卡拉状态单子的许多例子。有this example,但它很难理解,似乎只有一个other question堆栈溢出。斯卡拉斯状态单子例子
我打算发表一些我玩过的例子,但我会欢迎更多的例子。此外,如果有人可以提供例子,为什么init
,modify
, put
和gets
用于这将是伟大的。
编辑:here是一个很棒的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
将推断类型:
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)
}
现在使用遍历超过tenDoubleThrows
申请freqSum
。 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]()))
我无意中发现了一个有趣的博客文章Grok Haskell Monad Transformers从siGFP转已经通过一个单子转换应用两种状态单子的例子。这是一个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)
所以我这里使用init
和modify
的一个例子。在玩了一下之后,init[S]
原来真的很方便生成一个State[S,S]
的值,但是它允许的另一件事是访问理解中的状态。 modify[S]
是一种方便的方式来改变理解中的状态。所以上面的示例中可以读作:
a <- init[Int]
:与Int
状态开始,将其设置为通过State[Int, _]
单子包裹的值,并将其绑定到a
_ <- modify[Int](_ + 1)
:递增Int
状态b <- init[Int]
:取Int
状态并将其绑定到b
(与a
相同,但现在状态递增)State[Int, (Int, Int)]
值usi ng a
和b
。for comprehension语法已经使侧的State[S, A]
工作变得微不足道。init
,modify
,put
和gets
提供了一些工具在State[S, A]
的S
侧工作。
在博客文章中第二个例子翻译为:
val test2 = for {
a <- init[String]
_ <- modify[String](_ + "1")
b <- init[String]
} yield (a, b)
val go2 = test2 ! "0"
// (String, String) = ("0","01")
大同小异解释test1
。
第三个例子比较棘手,我希望有更简单的东西,我还没有发现。
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
允许我们在任意monad上添加状态转换M
。在这种情况下,状态是一个Int
,递增。如果我们调用stTrans ! 0
,那么我们最终会得到M[String]
。在我们的例子中,M
是StateString
,所以我们将以StateString[String]
结尾,即State[String, String]
。
这里棘手的部分是我们想从stTrans
中提取Int
状态值。这是initT
的用途。它只是创建一个对象,以我们可以使用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)))
所以我们几乎没有碰到老板,其中一个单位已经死亡,RIP。
这里的要点是构图。 State
(这只是一个函数S => (A, S)
)允许您定义产生结果的动作,以及在不知道状态来自何处的情况下操纵某些状态。 的Monad
部分给出了组成,以你的行动可以组成:
A => State[S, B]
B => State[S, C]
------------------
A => State[S, C]
等。
P.S.至于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
,或者你可以单独使用它们。
是不是“国家”monad而不是“国家变压器”在reallity?作为第二个问题:是否有更好的方法将掷骰子和总结合并为一个单一的国家monad?考虑到两个monad,你会怎么做? – ziggystar
@ziggystar,技术上'StateFreq'和'StateRandom'是单子。我不认为'State [S,x]'是一个monad变压器,因为'S'不需要是monad。为了更好地结合,我也想知道。我没有看到任何显而易见的东西。可能是'stateT'可以帮助,但我还没有弄清楚。 – huynhjl
我没有写“monad变压器”,而是“状态变压器”。 “国家[S,X]”的对象并不是一个国家,而是一个国家的转变。只是我认为这个名字可以被选择的更少。这不关你的答案,而是斯卡拉斯。 – ziggystar