Wednesday, May 6, 2015

Yesterday I also learned... Functional exception handling and sequence to varargs

Got this working at the end of the day yesterday and didn't get a chance to post it until now:

I experienced a network glitch doing an SFTP put, so I wanted to implement a retry wrapper that I could pass any code block to, along with the number of times to try before failing.
I wanted to pass in the types of exceptions to catch in the tries.
When I tried implementing it and passing in a variable type to the standard try/catch{case} statement (each case specifies what type(s) of exception to catch), it failed to compile.

There is an alternative way to catch exceptions in Scala -- the scala.util.control.Exception.handling() method.

handling(exceptionType1, exceptionType2, ...)
.by(ex => handleBlock)
.apply({
codeBlockToTry
})
}

In this way I was able to pass in a sequence of exception classes, cast it to _* to convert it to a variable-length parameters format (e.g., like args), and only catch the necessary types.

The parameter in my method that specifies the exception classes has a default value of a single-eleemnt sequence containing RuntimeException, and there is a type constraint indicating that all classes in the sequence must implement Throwable.

The retry method is a curried function, e.g., f(params1)(params2). The first sequence of parameters is applied, yielding a function against the second sequence of parameters.

The code is below:

import scala.util.control.Exception._
/** * Created by Samer Adra on 5/5/2015. */object RetryHelper {
/** * Retry any block of code up to a max number of times, optionally specifying the type of exception to retry. * @param maxTries Number of times to try before throwing the exception. * @param exceptionTypesToRetry Types of exception to retry. Defaults to single-element sequence containing classOf[RuntimeException] * @param codeToRetry Block of code to try * @tparam T Return type of block of code to try * @return Return value of block of code to try (else exception will be thrown if it failed all tries) */ def retry[T](maxTries: Int, exceptionTypesToRetry: Seq[Class[_ <: Throwable]] = Seq(classOf[RuntimeException]))(codeToRetry: => T): T = {
var result: Option[T] = None
var left = maxTries
while(!result.isDefined) {
left = left - 1
// try/catch{case...} doesn't seem to support dynamic exception types, so using handling block instead.
handling(exceptionTypesToRetry:_*)
.by(ex => if (left <= 0) throw ex).apply({
println("Left: " + left)
result = Some(codeToRetry)
})
}
result.get
}
}

No comments:

Post a Comment