scala - 如何在 Behaviors.receive 中进行递归调用?
问题描述
此代码来自 akka 文档。它使用推荐的函数式风格来引入演员:
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
}
}
}
Actor 包含一个递归调用“counter(newValue)”以通过函数方式维护可变状态。当我实现它并将@tailrec 注释添加到函数时,scala 编译器会抱怨调用不是尾递归,即使它似乎在最后一个位置。这意味着,迟早会发生堆栈溢出异常(假设您只想计算所有传入消息并且有数十亿条消息 - 没有 Java 堆栈足够大)。
是否可以使调用尾递归,或者我是否必须使用可变变量回退到面向对象的样式来处理这些情况?
解决方案
简短的回答是它不是递归的,因为counter
所做的最终归结为:
- 它创建一个实例
Function2[ActorContext[Command], Command, Behavior[Command]]
- 它将该实例传递给
Behaviors.receive
,后者使用它来构造一个Behaviors.Receive[Command]
对象(扩展Behavior[Command]
)
详细说明:
虽然这不是任何最近的 Scala 编译器执行的确切转换,但这应该让您了解它为什么不是递归的
object Counter {
// Protocol omitted
class CounterFunction(n: Int) extends Function2[ActorContext[Command], Command, Behavior[Command]] {
override def apply(context: ActorContext[Command], message: Command): Behavior[Command] =
message match {
case Increment =>
// omitting logging, etc.
counter(n + 1)
case GetValue(replyTo) =>
replyTo ! Value(n)
Behaviors.same
}
}
private def counter(n: Int): Behavior[Command] = {
val f = new CounterFunction(n)
Behaviors.receive(f)
}
}
请注意,由于调用counter
被包装在CounterFunction
'sapply
方法中,它们在被调用之前不会发生,apply
直到实际处理消息时才会发生。
这不会溢出堆栈,从这个与 Akka 内部深处的实现没有什么不同的东西的最小实现可以看出:
case class Behavior[T](
processor: (ActorContext[T], T) => Behavior[T]
)
object Behavior {
def processMsgs[T](b: Behavior[T], ctx: ActorContext[T])(msgs: List[T]): Behavior[T] =
// No recursion here...
msgs.foldLeft(b) { (behavior, m) => behavior.processor(ctx, m) }
}
该Behavior.processMsgs
函数是已知的示例(尤其是在函数式编程语言实现社区中),作为蹦床:
一个迭代调用[返回未评估函数对象的函数]的循环......程序员可以使用蹦床函数在面向堆栈的编程语言中实现尾递归函数调用。
在这种特殊情况下,“未评估的函数对象”processor
在此示例实现中是Behavior
.
推荐阅读
- go - dog 没有实现宠物(speak 方法的类型错误)
- performance - 一个计算如何从经过的时间计算 FLOPS?
- reactjs - 如何在 ReactJS 中路由到新页面
- r - ggplot的中位数统计差异
- javascript - 如何在页面加载时请求,反应钩子
- javascript - 如何将变量添加到 xhr.open outta Chrome 扩展?
- powerquery - 在 PowerQuery 中将列强制转换为其他类型
- python - 如何在一个目录中为一个项目创建多个 docker 镜像?
- node.js - 无法从端点下载 GraphQL 架构
- javascript - 那么 Javascript 库已经带有 GUI 了吗?