Question

I have some Akka-based actor system where multiple kind of actors is based on same template due to a fact that those actors only differ by a type of response value. For example:

final case class Request(data: Any)
final case class Response(data: Any)

abstract class ActorTemplate[T] extends Actor {
  def dataFunction: PartialFunction[Any, T]

  def respond(data: T): Response

  def receive: Receive = {
    case Request(data) if dataFunction.isDefinedAt(data) =>
      sender ! respond(dataFunction(data))
  }      
}

Partial function there is a method to avoid type erasure, which prevents me from simple

  def receive: Receive = {
    case Request(data: T) =>
      sender ! respond(data)

and at the same time force me to produce if dataFunction.isDefinedAt(data) guard on pattern and I simply don't like it very much.

Is there any means to avoid explicit guarding?

Only way I found for now is to introduce some silly extractor:

object DataExtractor {
  def unapply(data: Any): Option[T] =
    if (dataFunction.isDefinedAt(data)) Some(dataFunction(data)) else None
}

def receive: Receive = {
  case Request(DataExtractor(data) =>
    sender ! respond(data)

But maybe it is done already somewhere in standard library? Or maybe there is some other way similar to collections collect method, but for matching?

Conclusion

After some thoughts I've went back to extractor object and moved it into a trait with help of @pagoda_5b suggestions:

trait PFExtract[T] {
 object PF {
   def unapply(any: Any)(implicit pf: PartialFunction[Any, T]): Option[T] =
     pf.lift(any)
 }
}
Was it helpful?

Solution

What I would actually do is use the applyOrElse method on dataFunction, defining a default response when the partial function is not defined for the given input

def defaultAnswer: T

def receive: Receive = {
  case Request(data) =>
    sender ! respond(dataFunction.applyOrElse(data, defaultAnswer))
}

If you prefer to wrap your answer in a Option, there's already a method called lift that does that for you, as you correctly guessed

def receive: Receive = {
  case Request(data) =>
    sender ! respond(dataFunction.lift(data))
}

You can even decide not to answer if the function is not defined for data

def receive: Receive = {
  case Request(data) =>
    dataFunction.lift(data).foreach {
      sender ! respond(_)
    }
}

foreach defined on the Option type runs the code within the closure only if there's some content available (i.e. if you have a Some(...))

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top