3

我开始使用 state monad 来清理我的代码。我已经解决了我的问题,我处理了一个名为 CDR 的事务并相应地修改了状态。使用此功能执行状态更新,对于单个事务来说它工作得非常好。

def addTraffic(cdr: CDR): Network => Network = ...

这是一个例子:

scala> val processed: (CDR) => State[Network, Long] = cdr =>
 |   for {
 |     m <- init
 |     _ <- modify(Network.addTraffic(cdr))
 |     p <- get
 |   } yield p.count
processed: CDR => scalaz.State[Network,Long] = $$Lambda$4372/1833836780@1258d5c0

scala> val r = processed(("122","celda 1", 3))
r: scalaz.State[Network,Long] = scalaz.IndexedStateT$$anon$13@4cc4bdde

scala> r.run(Network.empty)
res56: scalaz.Id.Id[(Network, Long)] = (Network(Map(122 -> (1,0.0)),Map(celda 1 -> (1,0.0)),Map(1 -> Map(1 -> 3)),1,true),1)

我现在要做的是在迭代器上链接许多事务。我发现了一些效果很好但状态转换不需要输入的东西(通过 RNG 改变状态)

  import scalaz._
  import scalaz.std.list.listInstance
  type RNG = scala.util.Random

  val f = (rng:RNG) => (rng, rng.nextInt)
  val intGenerator: State[RNG, Int] = State(f)
  val rng42 = new scala.util.Random
  val applicative = Applicative[({type l[Int] = State[RNG,Int]})#l]

  // To generate the first 5 Random integers
  val chain: State[RNG, List[Int]] = applicative.sequence(List.fill(5)(intGenerator))
  val chainResult: (RNG, List[Int]) = chain.run(rng42)
  chainResult._2.foreach(println)

我没有成功地尝试适应这一点,但我无法让他们类型签名匹配,因为我的状态函数需要 cdr(事务)输入

谢谢

4

1 回答 1

4

TL;DR
您可以使用具有以下签名的函数从 s)traverseTraverse类型类中. 结果将是一个. 或者,如果您不关心那里,您可以改用,它将返回. 最后,如果您想在结果出现时“聚合”结果并形成 a,您可以使用from,它将返回,链中所有 s 的组合(例如折叠)结果ListCDRCDR => State[Network, Long]State[Network, List[Long]]List[Long]traverse_State[Network, Unit]TTMonoidfoldMapFoldableState[Network, T]TT

一个代码示例
现在一些更多的细节,带有代码示例。我将使用CatsState而不是 Scalaz 来回答这个问题,因为我从未使用过后者,但概念是相同的,如果您仍然有问题,我会挖掘出正确的语法。

假设我们有以下数据类型和导入可以使用:

import cats.implicits._
import cats.data.State

case class Position(x : Int = 0, y : Int = 0)

sealed trait Move extends Product
case object Up extends Move
case object Down extends Move
case object Left extends Move
case object Right extends Move

很明显,Position表示 2D 平面中的一个点,并且 aMove可以向上、向下、向左或向右移动该点。

现在,让我们创建一个方法,让我们可以看到我们在给定时间的位置:

def whereAmI : State[Position, String] = State.inspect{ s => s.toString }

以及改变我们位置的方法,给定一个Move

def move(m : Move) : State[Position, String] = State{ s => 
  m match {
    case Up => (s.copy(y = s.y + 1), "Up!")
    case Down => (s.copy(y = s.y - 1), "Down!")
    case Left => (s.copy(x = s.x - 1), "Left!")
    case Right => (s.copy(x = s.x + 1), "Right!")
  }
}

请注意,这将返回一个String,移动的名称后跟一个感叹号。这只是为了模拟从类型Move到其他类型的更改,并显示结果将如何聚合。稍后再详细介绍。

现在让我们尝试使用我们的方法:

val positions : State[Position, List[String]] = for{
  pos1 <- whereAmI 
  _ <- move(Up)
  _ <- move(Right)
  _ <- move(Up)
  pos2 <- whereAmI
  _ <- move(Left)
  _ <- move(Left)
  pos3 <- whereAmI
} yield List(pos1,pos2,pos3)

我们可以给它一个初始值Position并查看结果:

positions.runA(Position()).value // List(Position(0,0), Position(1,2), Position(-1,2))

(你可以忽略.value那里,这是一个怪癖,因为它State[S,A]实际上只是 的别名StateT[Eval,S,A]

如您所见,这与您预期的一样,您可以创建不同的“蓝图”(例如状态修改序列),一旦提供初始状态,就会应用这些蓝图。

现在,要实际回答您的问题,假设我们有 aList[Move]并且我们希望将它们按顺序应用于初始状态,并得到结果:我们使用traverse来自Traversetype-class

val moves = List(Down, Down, Left, Up)
val result : State[Position, List[String]] = moves.traverse(move)
result.run(Position()).value // (Position(-1,-1),List(Down!, Down!, Left!, Up!))

或者,如果您根本不需要AList在您的情况下),您可以使用traverse_, 代替,traverse结果类型将是:

val result_ : State[Position, List[String]] = moves.traverse_(move)
result_.run(Position()).value // (Position(-1,-1),Unit)

最后,如果您AState[S,A]表单中键入 a Monoid,那么您也可以使用foldMapfromFoldable来组合(例如折叠)所有A计算的 s。一个简单的例子(可能没用,因为这只会连接所有Strings)是这样的:

val result : State[Position,String] = moves.foldMap(move)
result.run(Position()).value // (Position(-1,-1),Down!Down!Left!Up!)

最后一种方法对您是否有用,实际上取决于A您拥有什么以及将其组合起来是否有意义。

这应该是您在场景中所需要的。

于 2018-12-16T22:53:10.760 回答