diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala index e9e2166efd..43e460a316 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala @@ -24,6 +24,7 @@ import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors //#fun-style import akka.actor.typed.scaladsl.AbstractBehavior +import org.slf4j.Logger //#oo-style object StyleGuideDocExamples { @@ -522,6 +523,54 @@ object StyleGuideDocExamples { } } + object BehaviorCompositionWithPartialFunction { + + //#messages-sealed-composition + sealed trait Command + case object Down extends Command + final case class GetValue(replyTo: ActorRef[Value]) extends Command + final case class Value(n: Int) + //#messages-sealed-composition + + //#get-handler-partial + def getHandler(value: Int): PartialFunction[Command, Behavior[Command]] = { + case GetValue(replyTo) => + replyTo ! Value(value) + Behaviors.same + } + //#get-handler-partial + + //#set-handler-non-zero-partial + def setHandlerNotZero(value: Int): PartialFunction[Command, Behavior[Command]] = { + case Down => + if (value == 1) + zero + else + nonZero(value - 1) + } + //#set-handler-non-zero-partial + + //#set-handler-zero-partial + def setHandlerZero(log: Logger): PartialFunction[Command, Behavior[Command]] = { + case Down => + log.error("Counter is already at zero!") + Behaviors.same + } + //#set-handler-zero-partial + + //#top-level-behaviors-partial + val zero: Behavior[Command] = Behaviors.setup { context => + Behaviors.receiveMessagePartial(getHandler(0).orElse(setHandlerZero(context.log))) + } + + def nonZero(capacity: Int): Behavior[Command] = + Behaviors.receiveMessagePartial(getHandler(capacity).orElse(setHandlerNotZero(capacity))) + + // Default Initial Behavior for this actor + def apply(initialCapacity: Int): Behavior[Command] = nonZero(initialCapacity) + //#top-level-behaviors-partial + } + object NestingSample1 { sealed trait Command diff --git a/akka-docs/src/main/paradox/typed/style-guide.md b/akka-docs/src/main/paradox/typed/style-guide.md index dcc8f5182c..c2f64e772a 100644 --- a/akka-docs/src/main/paradox/typed/style-guide.md +++ b/akka-docs/src/main/paradox/typed/style-guide.md @@ -412,6 +412,35 @@ Scala @@@ div {.group-scala} +## How to compose Partial Functions + +Following up from previous section, there are times when one might want to combine different `PartialFunction`s into one `Behavior`. + +A good use case for composing two or more `PartialFunction`s is when there is a bit of behavior that repeats across different states of the Actor. Below, you can find a simplified example for this use case. + +The Command definition is still highly recommended be kept within a `sealed` Trait: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #messages-sealed-composition } + +In this particular case, the Behavior that is repeating over is the one in charge to handle +the `GetValue` Command, as it behaves the same regardless of the Actor's internal state. +Instead of defining the specific handlers as a `Behavior`, we can define them as a `PartialFunction`: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #get-handler-partial #set-handler-non-zero-partial #set-handler-zero-partial } + +Finally, we can go on defining the two different behaviors for this specific actor. For each `Behavior` we would go and concatenate all needed `PartialFunction` instances with `orElse` to finally apply the command to the resulting one: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #top-level-behaviors-partial } + +Even though in this particular example we could use `receiveMessage` as we cover all cases, we use `receiveMessagePartial` instead to cover potential future unhandled message cases. + +@@@ + +@@@ div {.group-scala} + ## ask versus ? When using the `AskPattern` it's recommended to use the `ask` method rather than the infix `?` operator, like so: