Search code examples
scalasyntaxakkapartialfunction

How to return early in a pattern match of akka actor receive


Tried googling variations on this trivial question but didn't get an answer...

Basically I have a pattern match in my receive method. In some cases I want to break early from the receive handling

    override def receive = {
        case blah => {
          ... preflight code
          if (preflight failed) {
            sender() ! errorMSG 
            "break" or "return" here // get error "method receive has a return statement ; needs result type - 
// I tried adding Unit to the receive and return statements
          }
          ... more code
           ....

          if (something happened) {
            sender() ! anotherErrorMSG
            "break" or "return" here
          }
          ...
       }
       case foo => {...}
       case bar => {...}
     } // end receive

Solution

  • See this discussion of return's semantics and remember that receive returns a PartialFunction[Any, Unit] which is then evaluated after receive has returned. In short, there's no way to return early.

    Ömer Erden's solution of throwing an exception and using actor supervision works (indeed, exception throwing with all of its overhead is basically the only way to reliably end a computation early), but if you need any state to carry over from message to message, you'll need Akka persistence.

    If you don't want to nest if-elses as in chunjef's solution, you can use context.become and stash to create some spaghetti-ish code.

    But the best solution may be to have the things that might fail be their own functions with Either result types. Note that the Either API in scala 2.12 is quite a bit nicer than in previous versions.

    import scala.util.{ Either, Left, Right }
    
    type ErrorMsg = ...
    type PreflightSuccess = ... // contains anything created in preflight that you need later
    type MoreCodeSuccess = ... // contains anything created in preflight or morecode that you need later
    
    def preflight(...): Either[ErrorMsg, PreFlightSuccess] = {
      ... // preflight
      if (preflight failed)
        Left(errorMsg)
      else
        Right(...) // create a PreflightSuccess
    }
    
    def moreCode1(pfs: PreFlightSuccess): Either[ErrorMsg, MoreCodeSuccess] = {
      ... // more code
      if (something happened)
        Left(anotherErrorMSG)
      else
        Right(...) // create a MoreCodeSuccess
    }
    
    def moreCode2(mcs: MoreCodeSuccess): Either[ErrorMsg, Any] = {
      ... // more code, presumably never fails
      Right(...)
    }
    
    override def receive = {
      case blah =>
        val pf = preflight(...)
        val result = pf.map(morecode1).joinRight.map(moreCode2).joinRight // only calls morecode1 if preflight succeeded, and only calls morecode2 if preflight and morecode1 succeeded
        result.fold(
          { errorMsg => sender ! errorMsg },
          ()
        )
      case foo => ...
      case bar => ...
    }
    

    Whether this is preferable to nested if-else's is a question of taste...