Creating an Enumeratee from a stateful algorithm

蹲街弑〆低调 提交于 2020-01-02 09:56:30

问题


I have a stateful algorithm that incrementally takes input and incrementally produces output. The inputs and outputs are unrelated in number; i.e. an input may produce zero or more outputs.

I am attempting to turn it into an Enumeratee in the Play Framework, but I am having difficulty getting started.

My algorithm has local mutable state and synchronous operations and looks something like this

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")
}

I've studied the map, filter, etc. implementations in Enumeratee.scala, and I sort of understand them. But I'm having trouble seeing how to write my own implementation of something more complicated.

Could you describe/demonstrate how I can transform this algorithm into an Enumeratee?


回答1:


The only thing you need to implement is the applyOn method:

def applyOn[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] = ...

Everything else is implemented in the trait.

When creating an iteratee, I find recursion is the most important trick; it's a continuation-like style where rather than returning anything, each step computes the thing it needs to compute and then calls into it again. So your state should become a function parameter:

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)}

Notice how we return either a direct recursive call to next, or a continuation that will eventually make a (mutually) recursive call to next.

I've passed the Input around explicitly to every call, whereas a more elegant approach might handle it in the continuation (and perhaps implement applyOn directly rather than having it as a wrapper method), but hopefully this makes it clear what's going on. I'm sure there are more elegant ways to achieve the desired result (I've used scalaz iteratees but I don't know the Play API at all), but it's nice to work through the explicit solution at least once so we understand what's really going on underneath.



来源:https://stackoverflow.com/questions/27759548/creating-an-enumeratee-from-a-stateful-algorithm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!