在 <...>.sequence 方法上列出状态单子列表 "fail fast"?
Cats a List of State Monads "fail fast" on <...>.sequence method?
假设我们有一个状态列表,我们想要对它们进行排序:
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)。
记录 - 我知道可以简单地编写一个尾递归来检查正在运行的每个状态,如果满足某些条件 - 停止递归.我只想知道是否有更优雅的方法来做到这一点?递归解决方案对我来说似乎有很多样板,我错了吗?
谢谢!:))
这里有两件事需要完成。
首先是了解实际发生的事情:
State
获取一些状态值,许多组合调用之间的线程以及进程中的线程也会产生一些输出值
- 在你的例子中
Machine
是 状态 在调用之间线程化,而 MachineState
是单个操作的输出
sequence
(通常)采用一些参数化内容的集合(此处 List
)here State[Machine, _]
并在左侧进行嵌套(此处:List[State[Machine, _]]
-> 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$$$Lambda69/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
(在您的code nothing 告诉代码熔断的条件是什么以及应该如何执行)。我建议用其他一些函数明确地做到这一点,例如:
@ {
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
中对 运行 代码的需求 - 但您无法真正摆脱将状态组合在一起的代码,因此此 reduce
逻辑将在运行时应用n-1 次(其中 n
是您列表的大小),这无济于事。
顺便说一句,如果你仔细看看 Either
你会发现它也会计算 n
结果,然后才将它们组合起来,这样看起来像是断路,但实际上不是'吨。序列正在组合 "parallel" 计算的结果,但如果其中任何一个失败,则不会中断它们。
假设我们有一个状态列表,我们想要对它们进行排序:
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)。
记录 - 我知道可以简单地编写一个尾递归来检查正在运行的每个状态,如果满足某些条件 - 停止递归.我只想知道是否有更优雅的方法来做到这一点?递归解决方案对我来说似乎有很多样板,我错了吗?
谢谢!:))
这里有两件事需要完成。
首先是了解实际发生的事情:
State
获取一些状态值,许多组合调用之间的线程以及进程中的线程也会产生一些输出值- 在你的例子中
Machine
是 状态 在调用之间线程化,而MachineState
是单个操作的输出 sequence
(通常)采用一些参数化内容的集合(此处List
)here State[Machine, _]
并在左侧进行嵌套(此处:List[State[Machine, _]]
->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$$$Lambda69/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
(在您的code nothing 告诉代码熔断的条件是什么以及应该如何执行)。我建议用其他一些函数明确地做到这一点,例如:
@ {
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
中对 运行 代码的需求 - 但您无法真正摆脱将状态组合在一起的代码,因此此 reduce
逻辑将在运行时应用n-1 次(其中 n
是您列表的大小),这无济于事。
顺便说一句,如果你仔细看看 Either
你会发现它也会计算 n
结果,然后才将它们组合起来,这样看起来像是断路,但实际上不是'吨。序列正在组合 "parallel" 计算的结果,但如果其中任何一个失败,则不会中断它们。