如何在 Behaviors.receive 中进行递归调用? [英] How to make recursive calls within Behaviors.receive?

查看:52
本文介绍了如何在 Behaviors.receive 中进行递归调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此代码来自 akka 文档.它使用推荐的函数风格来实现一个actor:

This code is from akka documentation. It impelements an actor using the recommended functional style:

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 堆栈足够大).

The actor contains a recursive call "counter(newValue)" to maintain mutable state by functional means. When I implement this and add the @tailrec annotation to the function, the scala compiler complains as the call is not tail recursive, even it seems to be in the last position. This means, sooner or later a stack overflow exception will occur (imagine you just want to count all incoming messages and there are some billions of them - no java stack would be big enough).

是否可以使调用尾递归,或者我是否必须回退到具有可变变量的面向对象风格来处理这些情况?

Is it possible to make the call tail recursive or do I have to fallback to the object oriented style with mutable variables to handle those cases?

推荐答案

简短的回答是它不是递归的,因为 counter 所做的最终归结为:

The short answer is that it's not recursive, because what counter is doing, ultimately, boils down to:

  • 它创建了一个 Function2[ActorContext[Command], Command, Behavior[Command]]
  • 它将该实例传递给 Behaviors.receive,后者使用它来构造一个 Behaviors.Receive[Command] 对象(它扩展了 Behavior[Command])
  • It creates an instance of a Function2[ActorContext[Command], Command, Behavior[Command]]
  • It passes that instance to Behaviors.receive, which uses it to construct a Behaviors.Receive[Command] object (which extends Behavior[Command])

详细说明:

虽然这不是任何最近的 Scala 编译器执行的精确转换,但这应该让您了解为什么它不是递归的

While this isn't the exact transformation performed by any recent Scala compiler, this should give you the flavor of why it's not recursive

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 的调用包含在 CounterFunctionapply 方法中,因此它们不会发生,直到 apply 被调用,直到消息真正被处理.

Note that since the call to counter is wrapped inside CounterFunction's apply method, they don't happen until that apply is called, which isn't until a message is actually being processed.

这不会溢出堆栈,正如这个最小的实现与 Akka 内部深处的实现没有什么不同:

This will not overflow the stack, as can be seen with this minimal implementation of something that's not that different from the implementation deep within Akka's internals:

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 函数是已知的示例(尤其是在函数式编程语言实现社区中),作为 蹦床:

The Behavior.processMsgs function is an example of what's known (especially in the functional programming language implementation community), as a trampoline:

一个循环调用[返回未求值函数对象的函数]......程序员可以使用蹦床函数在面向堆栈的编程语言中实现尾递归函数调用.

a loop that iteratively invokes [functions which return unevaluated function objects].... Programmers can use trampolined functions to implement tail-recursive function calls in stack-oriented programming languages.

在这种特殊情况下,未计算的函数对象"是 Behavior 示例实现中的 processor.

In this particular case, the "unevaluated function object" is the processor in this sample implementation of Behavior.

这篇关于如何在 Behaviors.receive 中进行递归调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆