Tomas Prado Tomas Prado - 2 months ago 17
Scala Question

Scala pattern matching always goes to default case

First of all, I'm relatively new to scala.

Consider the following simple class NullLogService which logs results to console.

package services

import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email

import scala.util.Try

object NullLogService {
case class Log(email: Email, amazonResult: Try[_])
case class LogError(email: Email, amazonException: Try[_])
}

class NullLogService extends Actor {
import NullLogService._

def receive = {
case Log(email, amazonResult) => println("Email to: " + email.to + ". AmazonResultSucess:" + amazonResult.isSuccess.toString + ". AmazonResult: " + amazonResult.get.toString)
case LogError(email, amazonException) => println("Error: Email to: " + email.to + ". AmazonException:" + amazonException.get.toString)
case default@_ => println("Default case: "+default.toString)
}
}


It prints
Default case: Log(TO:email@email.com,Success({MessageId: Fake_Sent_OK}))
always.

I don't know what is happening, because the types are the same (Log(Email,Try(_))) ! It should go to the "Log" case but always fall to the default case!

Driving me crazy.

Code involved

Main class:

import java.io.File
import java.util.concurrent.TimeUnit

import akka.actor.SupervisorStrategy.Resume
import akka.actor._
import akka.routing.RoundRobinPool
import akka.util.Timeout
import com.thenewmotion.akka.rabbitmq._
import com.typesafe.config.{ConfigFactory, ConfigParseOptions}
import model.Email
import services.EmailService.EmailInfo
import services.{EmailService, LogService, NullLogService}
import utils.StringUtils

import scala.concurrent.duration.Duration

object Main extends App {
implicit val system = ActorSystem()

val emailServiceRef: ActorRef = system.actorOf(RoundRobinPool(rate).withSupervisorStrategy(supervisorStrategy).props(Props(new EmailService)), "emailWorkerPool")
val logServiceRef = system.actorOf(RoundRobinPool(1).props(Props(new NullLogService)), "logWorker")
val email = new Email(stringMap.apply("USEREMAIL"), stringMap.apply("REPLYTO"), stringMap.apply("SUBJECT"), stringMap.apply("BODY"), unsubscribeURL)
emailServiceRef ! EmailInfo(email, logServiceRef)
}


EmailService:

package services

import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email
import services.LogService.{Log, LogError}

object EmailService {
case class EmailInfo(email: Email, logServiceRef: ActorRef)
}

class EmailService extends Actor {
import EmailService._

def receive = {
case EmailInfo(email, logServiceRef) =>
val emailResult = new SendRawEmailResult
emailResult.setMessageId("Fake_Sent_OK" )
val amazonResult = Try( emailResult )
logServiceRef ! Log(email, amazonResult)
}
}

Answer

Presumably you haven't removed LogService.Log and LogService.LogError, or else you would have gotten a compilation error for EmailService. In this case NullLogService shouldn't define its own messages, but use LogService's ones:

package services

import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email

import scala.util.Try
import LogService.{Log, LogError}

// no object NullLogService unless you need it for something else

class NullLogService extends Actor {
  import NullLogService._

  def receive = {
    case Log(email, amazonResult) => println("Email to: " + email.to + ". AmazonResultSucess:" + amazonResult.isSuccess.toString + ". AmazonResult: " + amazonResult.get.toString)
    case LogError(email, amazonException) => println("Error: Email to: " + email.to + ". AmazonException:" + amazonException.get.toString)
    case default@_ => println("Default case: "+default.toString)
  }
}

Then you can switch between different services which use the same protocol (say, NullLogService, Slf4jLogService, InMemoryLogService) without changing the imports every time.

Comments