Здесь необходимо сделать 2 вещи.
Первое - это понимание того, что на самом деле происходит:
State
принимает некоторое значение состояния, потоки между многими составными вызывает и в процессе выдает некоторое выходное значение, а также - , в вашем случае
Machine
- это состояние , пронизывающее между вызовами, в то время как MachineState
- это выход одной операции sequence
(обычно) берет коллекцию (здесь List
) некоторых параметров c stuff 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$$$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
- но вы не можете действительно избавиться от кода, который объединяет состояния вместе, поэтому reduce
logi c будет применяться во время выполнения n-1 раз (где n
- размер вашего списка), и с этим ничего не поделаешь.
Кстати, если вы присмотритесь к Either
вы обнаружите, что он также вычисляет n
результаты и только затем объединяет их так, что это выглядит как разрыв цепи, но на самом деле это не так. Последовательность объединяет результаты «параллельных» вычислений, но не прервет их, если какой-либо из них завершится неудачей.