这段代码来自akka文档。它使用推荐的函数样式实现一个actor:
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
object Counter {
sealed trait Command
case object Increment extends Command
final case class GetValue(replyTo: ActorRef[Value]) extends Command
final case class Value(n: Int)
def apply(): Behavior[Command] =
counter(0)
private def counter(n: Int): Behavior[Command] =
Behaviors.receive { (context, message) =>
message match {
case Increment =>
val newValue = n + 1
context.log.debug("Incremented counter to [{}]", newValue)
counter(newValue)
case GetValue(replyTo) =>
replyTo ! Value(n)
Behaviors.same
}
}
}
执行元包含递归调用“计数器(newValue)”来通过函数方式维护可变状态。当我实现这一点并向函数添加@tailrec注解时,scala编译器会抱怨调用不是尾部递归的,即使它似乎在最后一个位置。这意味着,迟早会发生堆栈溢出异常(假设您只想计算所有传入的消息,并且有数十亿条消息-没有Java堆栈足够大)。
有没有可能使调用尾递归,或者我必须退回到具有可变变量的面向对象样式来处理这些情况?
1条答案
按热度按时间enyaitl31#
简单地说,它不是递归的,因为
counter
所做的事情最终归结为:Function2[ActorContext[Command], Command, Behavior[Command]]
示例Behaviors.receive
,Behaviors.receive
使用该示例构造Behaviors.Receive[Command]
对象(该对象扩展Behavior[Command]
)详细说明:
虽然这不是任何最近的Scala编译器执行的完全相同的转换,但这应该可以让您了解为什么它不是递归的
请注意,由于对
counter
的调用被 Package 在CounterFunction
的apply
方法中,因此只有在apply
被调用(实际上是在处理消息之前)时,才会发生这些操作。这将不会溢出堆栈,这可以从这个最小的实现中看出,它与Akka内部的实现没有太大的不同:
Behavior.processMsgs
函数是一个已知的例子(尤其是在函数式编程语言实现社区),就像一个蹦床:一个迭代调用[返回未求值函数对象的函数]的循环......程序员可以使用蹦床函数在面向栈的编程语言中实现尾部递归函数调用。
在这个特定的例子中,“未求值的函数对象”是
Behavior
的示例实现中的processor
。