Создание Enumeratee из алгоритма с отслеживанием состояния
У меня есть алгоритм с состоянием, который постепенно принимает входные данные и постепенно производит выходные данные. Входы и выходы не связаны по количеству; т.е. вход может производить ноль или более выходов.
Я пытаюсь превратить его в Enumeratee
в Play Framework, но у меня возникают трудности с началом работы.
Мой алгоритм имеет локальное изменяемое состояние и синхронные операции и выглядит примерно так
var state = 'foo
var i = input()
while (i != null) {
if (state == 'foo && i >= 0) {
// 2 outputs, and change state
output(i - 2)
output(i * 3)
state = 'bar
} else if (state == 'foo) {
// error
error("expected >= 0")
} else if (state == 'bar) {
// 0 outputs, and change state
state = 'foo
}
... // etc
i = input()
}
if (state != 'bar) {
error("unexpected end")
}
Я изучал map
, filter
и т.д. реализации в Enumeratee.scala
и я вроде их понимаю. Но я не могу понять, как написать собственную реализацию чего-то более сложного.
Не могли бы вы описать / продемонстрировать, как я могу превратить этот алгоритм в Enumeratee
?
1 ответ
Единственное, что вам нужно реализовать, это applyOn
метод:
def applyOn[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] = ...
Все остальное реализовано в черте.
Когда я создаю итерацию, я считаю, что рекурсия - самый важный трюк; это стиль, подобный продолжению, где вместо того, чтобы возвращать что-либо, каждый шаг вычисляет то, что ему нужно для вычисления, а затем вызывает его снова. Таким образом, ваше состояние должно стать параметром функции:
def next[A](inner: Iteratee[To, A], i: Input[From], state: Symbol)
: Iteratee[From, A] =
i match {
case Input.El(e) =>
if(state == 'foo && e >= 0) {
val nextInner = Iteratee.flatten {
inner.feed(Input.El(e - 2)) flatMap {_.feed(Input.El(e * 3))}
}
val nextState = 'bar
Cont {k => next(nextInner, k, nextState)}
} else if(state == 'foo)
Error("expected >=0", i)
else if(state == 'bar)
next(inner, i, 'foo)
...
case _ =>
//pass through Empty or Eof
Iteratee.flatten(inner.feed(i))
}
def applyOn[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] =
Cont {i => next(inner, i, 'foo)}
Обратите внимание, как мы возвращаем прямой рекурсивный вызов next
или продолжение, которое в конечном итоге сделает (взаимно) рекурсивный вызов next
,
Я прошел Input
явно для каждого вызова, тогда как более элегантный подход может обработать его в продолжении (и, возможно, реализовать applyOn
напрямую, а не как метод обертки), но, надеюсь, это прояснит, что происходит. Я уверен, что есть более элегантные способы достижения желаемого результата (я использовал итераторы скалаза, но я совсем не знаю API Play), но приятно поработать с явным решением хотя бы один раз, чтобы мы поняли, что действительно происходит внизу.