Search code examples
scalatry-catchscala-option

Chaining scala Try instances that contain Options


I am trying to find a cleaner way to express code that looks similar to this:

def method1: Try[Option[String]] = ???
def method2: Try[Option[String]] = ???
def method3: Try[Option[String]] = ???

method1 match
{
  case f: Failure[Option[String]] => f
  case Success(None) =>
  method2 match
    {
      case f:Failure[Option[String]] => f
      case Success(None) =>
      {
        method3
      }
      case s: Success[Option[String]] => s
    }
  case s: Success[Option[String]] => s
}

As you can see, this tries each method in sequence and if one fails then execution stops and the base match resolves to that failure. If method1 or method2 succeeds but contains None then the next method in the sequence is tried. If execution gets to method3 its results are always returned regardless of Success or Failure. This works fine in code but I find it difficult to follow whats happening.

I would love to use a for comprehension

for
{
  attempt1 <- method1
  attempt2 <- method2
  attempt3 <- method3
}
  yield
{
  List(attempt1, attempt2, attempt3).find(_.isDefined)
}

because its beautiful and what its doing is quite clear. However, if all methods succeed then they are all executed every time, regardless of whether an earlier method returns a usable answer. Unfortunately I can't have that.

Any suggestions would be appreciated.


Solution

  • scalaz can be of help here. You'll need scalaz-contrib which adds a monad instance for Try, then you can use OptionT which has nice combinators. Here is an example:

    import scalaz.OptionT
    import scalaz.contrib.std.utilTry._
    import scala.util.Try
    
    def method1: OptionT[Try, String] = OptionT(Try(Some("method1")))
    def method2: OptionT[Try, String] = OptionT(Try(Some("method2")))
    def method3: OptionT[Try, String] = { println("method 3 is never called") ; OptionT(Try(Some("method3"))) }
    def method4: OptionT[Try, String] = OptionT(Try(None))
    def method5: OptionT[Try, String] = OptionT(Try(throw new Exception("fail")))
    
    println((method1 orElse method2 orElse method3).run) // Success(Some(method1))
    println((method4 orElse method2 orElse method3).run) // Success(Some(method2))
    println((method5 orElse method2 orElse method3).run) // Failure(java.lang.Exception: fail)