斯卡拉兹州monad的例子

我还没有看到斯卡拉州单子的许多例子。 有这个例子,但它很难理解,似乎只有一个堆栈溢出问题 。

我将会发布一些我玩过的例子,但是我会欢迎更多的例子。 另外如果有人可以提供例子,为什么initmodifyputgets用于这将是伟大的。

编辑: 这是一个关于状态monad的真棒2小时演示。

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

 import scalaz._ import Scalaz._ 

状态types被定义为State[S, A] ,其中S是状态的types, A是正在装饰的值的types。 创build状态值的基本语法使用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]]] 。 使用Statefunction…

 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)]] 。 我们可以提供一个types别名。

 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来推断types:

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

现在使用遍历来将freqSum应用于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来更简洁地推断出types:

 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]的types别名, StateT[Id, S, A] tenDoubleThrows2最终被input为Id 。 我使用copoint把它变回Listtypes。

总之,使用状态的关键似乎是让函数返回一个修改状态的函数和所需的实际结果值。 声明:我从来没有在生产代码中使用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]())) 

我偶然发现了一篇有趣的博客文章,来自sigfp的Grok Haskell Monad变形金刚 ,有一个通过monad变换器应用两个状态monad的例子。 这是一个scalaz翻译。

一个例子显示了一个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) 

所以我在这里有一个使用initmodify的例子。 在玩了一下之后, init[S]结果是生成一个State[S,S]值真的很方便,但是它允许的另一件事是访问理解中的状态。 modify[S]是一种方便的方式来转换内部的理解状态。 所以上面的例子可以被理解为:

  • a <- init[Int] :以一个Int状态开始,将其设置为由State[Int, _] monad包装的值,并将其绑定到
  • _ <- modify[Int](_ + 1) :递增Int状态
  • b <- init[Int] :采用Int状态并将其绑定到b (与b相同,但现在状态递增)
  • 使用ab产生一个State[Int, (Int, Int)]值。

理解语法已经使得在State[S, A]A方工作变得微不足道。 initmodifyputgets提供一些工具在S 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 stTrans ! 0我们会以M[String] 。 在我们的例子中, MStateString ,所以我们最后会得到State[String, String] StateString[String] State[String, String]

这里棘手的部分是我们想从stTrans提取Int状态值。 这是initT的用途。 它只是创build一个对象,使我们可以用stTrans flatMap来访问状态。

编辑:原来,所有的尴尬可以避免,如果我们真的重复使用test1test2方便地存储想要的状态在他们返回的元组的_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))) } 

当游戏开始时,我们要跟踪游戏状态,所以让我们用状态monad来定义我们的“动作”:

让我们狠狠地打击老板,让他失去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) } 

现在我们可以把这些动作组合起来:

 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] 

等等。

PS至于getputmodify区别:

modify可以看作是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时候,你在概念上使用getput ,或者你可以单独使用它们。

Interesting Posts