我有一个有状态算法,逐步获取输入并逐步产生输出。输入和输出的数量无关;即输入可能产生零个或多个输出。
我试图将其转换为Play框架中的Enumeratee
,但我很难开始使用。
我的算法具有本地可变状态和同步操作,看起来像这样
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")
}
我已经研究了Enumeratee.scala
中的map
,filter
等实现,我对它们有所了解。但是我很难看到如何编写我自己的更复杂的实现。
您能描述/演示如何将此算法转换为Enumeratee
吗?
答案 0 :(得分:2)
您需要实现的唯一方法是applyOn
方法:
def applyOn[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] = ...
其他一切都在特质中实现。
创建iteratee时,我发现递归是最重要的技巧;它是一种类似于延续的风格,而不是返回任何东西,每一步计算它需要计算的东西,然后再次调用它。所以你的状态应该成为一个函数参数:
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),但是至少一次通过显式解决方案很好,所以我们理解什么是真的在下面。