Создание 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?


person Paul Draper    schedule 03.01.2015    source источник


Ответы (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 напрямую, а не использовать его как метод-оболочку), но, надеюсь, это проясняет, что происходит. Я уверен, что есть более элегантные способы достижения желаемого результата (я использовал итерации scalaz, но я вообще не знаю Play API), но неплохо хотя бы раз проработать явное решение, чтобы мы поняли, что действительно происходит внизу.

person lmm    schedule 03.01.2015
comment
Хммм... У меня почти получилось... Iteratee#feed и Iteratee.flatten не хватало кусочков. Спасибо. Мне тоже интересно, есть ли какая-то лучшая форма с какой-либо функцией библиотеки Play, но это, безусловно, легко понять, и это помогает мне понять, что происходит. - person Paul Draper; 04.01.2015