使用状态 Monad 链接多个转换
Chaining a number of transitions with the state Monad
我开始使用状态 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] = $$Lambda72/1833836780@1258d5c0
scala> val r = processed(("122","celda 1", 3))
r: scalaz.State[Network,Long] = scalaz.IndexedStateT$$anon@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(交易)输入
谢谢
TL;DR
您可以在 CDR
的集合(例如 List
)上使用 Traverse
类型 class 中的 traverse
,使用具有此签名的函数:CDR => State[Network, Long]
。结果将是 State[Network, List[Long]]
。或者,如果您不关心那里的 List[Long]
,您可以使用 traverse_
,这将 return State[Network, Unit]
。最后,如果你想要 "aggregate" 结果 T
并且 T
形成 Monoid
,你可以使用 foldMap
来自 Foldable
,这将 return State[Network, T]
,其中 T
是链中所有 T
的组合(例如折叠)结果。
代码示例
现在有更多细节,带有代码示例。我将使用 Cats State
而不是 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
表示二维平面中的一个点,Move
可以将这个点向上、向下、向左或向右移动。
现在,让我们创建一个方法,让我们能够看到我们在给定时间的位置:
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!")
}
}
请注意,这将 return 一个 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]
的别名)
如您所见,它的行为符合您的预期,您可以创建不同的 "blueprints"(例如,状态修改序列),一旦提供初始状态就会应用。
现在,为了真正回答你的问题,假设我们有一个 List[Move]
并且我们想将它们按顺序应用到初始状态,并得到结果:我们使用 traverse
来自 Traverse
type-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!))
或者,如果您根本不需要 A
(在您的情况下是 List
),您可以使用 traverse_
,而不是 traverse
和结果类型将是:
val result_ : State[Position, List[String]] = moves.traverse_(move)
result_.run(Position()).value // (Position(-1,-1),Unit)
最后,如果您在 State[S,A]
中输入的 A
形成一个 Monoid
,那么您也可以使用 Foldable
中的 foldMap
来组合(例如折叠) 所有 A
的计算结果。一个简单的例子(可能没用,因为这只会连接所有 String
s)是这样的:
val result : State[Position,String] = moves.foldMap(move)
result.run(Position()).value // (Position(-1,-1),Down!Down!Left!Up!)
最后一种方法对您是否有用,实际上取决于您所拥有的 A
以及结合使用它是否有意义。
这应该是您在场景中所需的全部内容。
我开始使用状态 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] = $$Lambda72/1833836780@1258d5c0
scala> val r = processed(("122","celda 1", 3))
r: scalaz.State[Network,Long] = scalaz.IndexedStateT$$anon@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(交易)输入
谢谢
TL;DR
您可以在 CDR
的集合(例如 List
)上使用 Traverse
类型 class 中的 traverse
,使用具有此签名的函数:CDR => State[Network, Long]
。结果将是 State[Network, List[Long]]
。或者,如果您不关心那里的 List[Long]
,您可以使用 traverse_
,这将 return State[Network, Unit]
。最后,如果你想要 "aggregate" 结果 T
并且 T
形成 Monoid
,你可以使用 foldMap
来自 Foldable
,这将 return State[Network, T]
,其中 T
是链中所有 T
的组合(例如折叠)结果。
代码示例
现在有更多细节,带有代码示例。我将使用 Cats State
而不是 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
表示二维平面中的一个点,Move
可以将这个点向上、向下、向左或向右移动。
现在,让我们创建一个方法,让我们能够看到我们在给定时间的位置:
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!")
}
}
请注意,这将 return 一个 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]
的别名)
如您所见,它的行为符合您的预期,您可以创建不同的 "blueprints"(例如,状态修改序列),一旦提供初始状态就会应用。
现在,为了真正回答你的问题,假设我们有一个 List[Move]
并且我们想将它们按顺序应用到初始状态,并得到结果:我们使用 traverse
来自 Traverse
type-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!))
或者,如果您根本不需要 A
(在您的情况下是 List
),您可以使用 traverse_
,而不是 traverse
和结果类型将是:
val result_ : State[Position, List[String]] = moves.traverse_(move)
result_.run(Position()).value // (Position(-1,-1),Unit)
最后,如果您在 State[S,A]
中输入的 A
形成一个 Monoid
,那么您也可以使用 Foldable
中的 foldMap
来组合(例如折叠) 所有 A
的计算结果。一个简单的例子(可能没用,因为这只会连接所有 String
s)是这样的:
val result : State[Position,String] = moves.foldMap(move)
result.run(Position()).value // (Position(-1,-1),Down!Down!Left!Up!)
最后一种方法对您是否有用,实际上取决于您所拥有的 A
以及结合使用它是否有意义。
这应该是您在场景中所需的全部内容。