Вот немного более подробная версия simulateMachine
, которая должна быть немного проще для понимания.
def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
_ <- State.sequence( // this _ means "discard this 'unpacked' value"
inputs.map(
(State.modify[Machine] _).compose(update) // this _ is an eta-expansion
)
)
s <- State.get
} yield (s.coins, s.candies)
Значение "Unpacked" - for { value <- container } ...
в основном "unpacks" value
от container
. Семантика распаковки в разных контейнерах различна - наиболее простой является семантика List
или Seq
, где value
каждый элемент семантики value
и for
в этом случае становится просто итерацией. Другие известные «контейнеры»:
Option
- for
семантика: значение присутствует или нет + модификации значения, если оно присутствует Try
- for
семантика : успешное вычисление или нет + модификации успешной Future
- for
семантика: определение цепочки вычислений для выполнения при наличии значения
Практически каждая монада ( объяснение, что такое монада, является haaard :) Попробуйте это в качестве отправной точки) определяет способ "распаковать" и "итерировать". Ваш State
практически является монадой (хотя он и не заявляет об этом явно) (и, кстати, есть каноническая State
монада, которую вы можете найти в сторонних библиотеках, таких как cats или scalaz )
Эта-расширение объясняется в этом SO ответ , но вкратце он преобразует функцию / метод в объект функции. Примерно эквивалентно x => State.modify[Machine](x)
или полностью отлаженной версии:
def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = {
val simulationStep: Input => State[Machine, Unit] = (State.modify[Machine] _).compose(update) // this _ is an eta-expansion
State.sequence(inputs.map(input => simulationStep(input)))
.flatMap(_ => State.get) // `_ => xyz` creates a function that does not care about it's input
.map(machine => (machine.candies, machine.coins))
}
Обратите внимание, что использование _
в _ => State.get
- это еще один способ использования _
- сейчас он создает функцию, которая не заботится о своем аргументе .
Теперь, отвечая на вопросы, когда они задаются:
- См. Выше:)
- Если вы посмотрите на десагаратную версию, вы увидите, что
State.get
передается как "обратный вызов" flatMap
. Есть несколько обручей, чтобы прыгнуть, чтобы проследить это (я оставлю это как упражнение для вас :)), но, по сути, это то, что он заменяет часть «результата» состояния (A
) состоянием сам (S
)). Хитрость заключается в том, что он вообще не получает Machine
- он просто «цепляет» вызов для «извлечения» Machine
из S
части состояния в A
часть состояния. - Я думаю, что версия от desugared должна быть достаточно понятной. Что касается версии для понимания -
for
исправляет «контекст», используя первый вызов в цепочке, поэтому все остальное (включая yield
) выполняется в этом «контексте» - в частности, оно устанавливается на State[_, _]
. yield
по существу эквивалентно вызову map
в конце цепочки, а в семантике State[_, _]
map заменяет часть A
, но сохраняет часть S
. Candy.simulateMachine(List(Coin, Turn, Coin, Turn)).run(Machine(false, 2, 0))
PS Ваш текущий код по-прежнему пропускает State.set
метод. Я предполагаю, что это просто def set[S](s: => S): State[S, S] = State(_ => (s, s))
- по крайней мере, добавив это, вы сможете компилировать ваш фрагмент в моей среде.