Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using a partial function for catch block with scala

Tags:

scala

I have a catch block that I end up repeating quite a bit, and found this SO question that confirmed that I can use a partial function as a catch block (What are the use cases for Scala 2.9's try…catch generalization? 1).

Currently, my code looks like this:

var task = newTask("update product", "update for customer " + customerId.toString)
try {
          val vcdRouter = actorSystem.actorFor("user/supervisor/router-10.10.10.10:443")

          val vdcId = new UUID("92ddba5e-8101-4580-b9a5-e3ee6ea5718f")
          val vdcGet = sendExpect[AdminVdcType](vcdRouter, GetVdc(vdcId))
          val vdcPut = VdcPutConfig(vdcGet, c)
          val vdcPutTask = sendExpect[TaskType](vcdRouter, UpdateVdc(vdcId, vdcPut))

          task = task.copy(Progress = 100, status = SuccessType)

        } catch {
          case failure: NoResponseBodyException =>
            logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
            task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, BadGateway)))

          case failure: VcdGatewayException ⇒
            logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
            task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, GatewayTimeout)))

          case failure: Exception ⇒
            logger.debug("*** In putCustomerProduct, got a Left(Exception)")
            task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure)))

        }

Since I have this task var that is changing inside the catch block, is there a nice way to make it accessible from within a partial function that holds the catch block? The task is a var since it sets some initialization data like a created timestamp upon entry into the system. I could work around this, but am interested in an answer to the original question anyway.

like image 663
jxstanford Avatar asked May 04 '12 16:05

jxstanford


3 Answers

I'm assuming that you have several different functions with different var tasks that you want to use this with.

You can create a function that takes both task and a task-setter as parameters, which returns a PartialFunction that you can use as your catch handler.

def handler(task: Task, setTask: Task => Any): PartialFunction[Throwable, Any] = {
  case failure: NoResponseBodyException =>
    logger.debug("*** In putCustomerProduct, got a Left(NoResponseBodyException)")
    setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, BadGateway))))

  case failure: VcdGatewayException =>
    logger.debug("*** In putCustomerProduct, got a Left(VcdGatewayException)")
    setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure, GatewayTimeout))))

  case failure: Exception =>
    logger.debug("*** In putCustomerProduct, got a Left(Exception)")
    setTask(task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(failure))))
}

// somewhere else in your code...
var task = newTask("update product", "update for customer " + customerId.toString)
try {
   val vcdRouter = actorSystem.actorFor("user/supervisor/router-10.10.10.10:443")

  val vdcId = new UUID("92ddba5e-8101-4580-b9a5-e3ee6ea5718f")
  val vdcGet = sendExpect[AdminVdcType](vcdRouter, GetVdc(vdcId))
  val vdcPut = VdcPutConfig(vdcGet, c)
  val vdcPutTask = sendExpect[TaskType](vcdRouter, UpdateVdc(vdcId, vdcPut))

  task = task.copy(Progress = 100, status = SuccessType)
} catch handler(task, task = _)

I also agree with user3001 that you should try to reduce the duplication in your catch handlers.

like image 99
leedm777 Avatar answered Oct 05 '22 00:10

leedm777


Here is an alternate way using scala.util.control.Exception.

scala> import util.control.Exception._
import util.control.Exception._

First create the Catch[_] objects to handle specific exceptions.

scala> val nfeh = handling(classOf[NumberFormatException]) by println
nfeh: util.control.Exception.Catch[Unit] = Catch()

scala> val aobeh = handling(classOf[ArrayIndexOutOfBoundsException]) by println
aobeh: util.control.Exception.Catch[Unit] = Catch()

Combine them together using .or method. Note that just like in catch block, order matters.

scala> val h = nfeh or aobeh
h: util.control.Exception.Catch[Unit] = Catch()

Apply the handlers to the code that may throw the exception.

scala> h apply {
     |   println("1o2".toInt)
     | }
java.lang.NumberFormatException: For input string: "1o2"

scala> h apply {
     |   val x = Array(8)
     |   println(x(2))
     | }
java.lang.ArrayIndexOutOfBoundsException: 2

As for the task part, you could do something along the following lines:

scala> val nfeh = handling(classOf[NumberFormatException]) by { ex =>
     |   println(ex)
     |   -1
     | }
nfeh: util.control.Exception.Catch[Int] = Catch()

scala> val aobeh = handling(classOf[ArrayIndexOutOfBoundsException]) by { ex =>
     |   println(ex)
     |   -2
     | }
aobeh: util.control.Exception.Catch[Int] = Catch()

scala> val h = nfeh or aobeh
h: util.control.Exception.Catch[Int] = Catch()

scala> val task = h apply {
     |   "120".toInt
     | }
task: Int = 120

scala> val task = h apply {
     |   "12o".toInt
     | }
java.lang.NumberFormatException: For input string: "12o"
task: Int = -1

scala> val task = h apply {
     |   Array(12, 33, 22)(2)
     | }
task: Int = 22

scala> val task = h apply {
     |   Array(12, 33, 22)(6)
     | }
java.lang.ArrayIndexOutOfBoundsException: 6
task: Int = -2
like image 24
missingfaktor Avatar answered Oct 05 '22 01:10

missingfaktor


I'm quite new to scala, so maybe my answer might not be that correct, but: In each "case" block, the only thing that changes is the error type. At first, I would create another match for the exception type. Something like

def exceptionToErrorReason(val failure:Exception) : ErrorReason = failure.getClass match {
  case classOf[NoResponseBodyException] => BadGateway
  case classOf[VcdGatewayException ] => GatewayTimeout
  case _ => null
}

Now you can change your catch block like this

case failure: Exception ⇒
        logger.debug("*** In putCustomerProduct, got a Left(" + failure.getClass().getSimpleName() + )")
        task = task.copy(Progress = 100, status = Error, Error = Option(exceptionToError(exceptionToErrorReason(failure)))

and leave out the rest. No need to duplicate anything.

like image 28
user3001 Avatar answered Oct 05 '22 00:10

user3001