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
?
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