Add how to compose Partial Behaviors #29832 (#29853)

* Add how to compose Partial Behaviors #29832

Refs: #29832

Adds 2 examples on how to compose partial behaviors:
- Using `receivePartial`
- Using `PartialFunction`

Examples are rewritten to follow the main one in the page.
Instead of composing through Functions of
`Command => Behavior[Command]` I decided to
use `Behaviors.receivePartial` as it seems more idiomatic.

Adds docs for both examples under the style guide page.

* Remove potentially dangerous `receivePartial` composition

Example now only showcases `PartialFunction` composition

* Remove unused import

* Streamline example
This commit is contained in:
Josep Prat 2020-12-08 13:05:24 +01:00 committed by GitHub
parent 2c659a046e
commit 388fb73beb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 0 deletions

View file

@ -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

View file

@ -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: