在<...>。sequence方法上列出状态单子状态“快速失败”?

时间:2020-03-30 12:01:48

标签: scala state sequence monads

假设我们有一个状态列表,我们想对它们进行排序:

import cats.data.State
import cats.instances.list._
import cats.syntax.traverse._


trait MachineState
case object ContinueRunning extends MachineState
case object StopRunning extends MachineState

case class Machine(candy: Int)

val addCandy: Int => State[Machine, MachineState] = amount =>
  State[Machine, MachineState] { machine =>
    val newCandyAmount = machine.candy + amount
    if(newCandyAmount > 10)
      (machine, StopRunning)
    else
      (machine.copy(newCandyAmount), ContinueRunning)
  }


List(addCandy(1),
     addCandy(2),
     addCandy(5),
     addCandy(10),
     addCandy(20),
     addCandy(50)).sequence.run(Machine(0)).value

结果应该是

(Machine(10),List(ContinueRunning, ContinueRunning, ContinueRunning, StopRunning, StopRunning, StopRunning))

很明显,最后3个步骤是多余的。 是否有办法使此序列尽早停止?在这里,当StopRunning返回时,我想停止。例如,两个Either的列表都会快速失败,并在需要时提前停止序列(因为它像monad一样)。

记录-我确实知道可以简单地编写一个尾部递归来检查正在运行的每个状态,如果满足某些条件,则可以停止递归。我只想知道是否有更优雅的方法?递归解决方案对我来说似乎是很多样板,对吗?

谢谢!:))

1 个答案:

答案 0 :(得分:1)

这里需要做两件事。

首先是了解实际情况:

  • State带有一些状态值,多个组合调用之间的线程,在此过程中也会产生一些输出值
  • 在您的情况下,Machine是调用之间的状态状态,而MachineState是单个操作的输出
  • sequence(通常)获取一些参数化内容List的集合(此处为here State[Machine, _]),然后在左侧(此处为List[State[Machine, _]]-> {{ 1}})(State[Machine, List[_]]是您要填补类型的空白)
  • 结果是您将在所有功能中使用状态(_),同时将每个功能(Machine(0))的输出组合到输出列表中
MachineState

换句话说,您想要的是断路,然后// ammonite // to better see how many times things are being run @ { val addCandy: Int => State[Machine, MachineState] = amount => State[Machine, MachineState] { machine => val newCandyAmount = machine.candy + amount println("new attempt with " + machine + " and " + amount) if(newCandyAmount > 10) (machine, StopRunning) else (machine.copy(newCandyAmount), ContinueRunning) } } addCandy: Int => State[Machine, MachineState] = ammonite.$sess.cmd24$$$Lambda$2669/1733815710@25c887ca @ List(addCandy(1), addCandy(2), addCandy(5), addCandy(10), addCandy(20), addCandy(50)).sequence.run(Machine(0)).value new attempt with Machine(0) and 1 new attempt with Machine(1) and 2 new attempt with Machine(3) and 5 new attempt with Machine(8) and 10 new attempt with Machine(8) and 20 new attempt with Machine(8) and 50 res25: (Machine, List[MachineState]) = (Machine(8), List(ContinueRunning, ContinueRunning, ContinueRunning, StopRunning, StopRunning, StopRunning)) 可能不是您想要的。

事实上,您可能还需要其他东西-将.sequence个函数的列表合并为一个函数,如果计算结果为A => (A, B),则该函数将停止下一个计算(在您的代码中什么也没有)告诉代码断路的情况是什么以及应如何执行)。我建议使用其他功能明确地执行此操作,例如:

StopRunning

这将消除在@ { List(addCandy(1), addCandy(2), addCandy(5), addCandy(10), addCandy(20), addCandy(50)) .reduce { (a, b) => a.flatMap { // flatMap and map uses MachineState // - the second parameter is the result after all! // we are pattern matching on it to decide if we want to // proceed with computation or stop it case ContinueRunning => b // runs next computation case StopRunning => State.pure(StopRunning) // returns current result without modifying it } } .run(Machine(0)) .value } new attempt with Machine(0) and 1 new attempt with Machine(1) and 2 new attempt with Machine(3) and 5 new attempt with Machine(8) and 10 res23: (Machine, MachineState) = (Machine(8), StopRunning) 中运行代码的需要-但您无法真正摆脱将状态组合在一起的代码,因此,此addCandy逻辑将在运行时应用n-1次(其中reduce是列表的大小),这无济于事。

顺便说一句,如果您仔细查看n,您会发现它还计算Either的结果,然后将它们组合起来,看起来好像是断路了,但实际上不是。序列将“并行”计算的结果组合在一起,但是如果其中任何一个失败,都不会中断它们。