Question

How can I (best) convert an Option returned by a method call into a Try (by preference, although an Either or a scalaz \/ or even a Validation might be OK) including specifying a Failure value if appropriate?

For example, I have the following code, which feels kludgy, but does at least do (most of) the job:

import scala.util._

case class ARef(value: String)
case class BRef(value: String)
case class A(ref: ARef, bRef: BRef)
class MismatchException(msg: String) extends RuntimeException(msg)

trait MyTry {

  // Given:
  val validBRefs: List[BRef]

  // Want to go from an Option[A] (obtained, eg., via a function call passing a provided ARef)
  // to a Try[BRef], where the b-ref needs to be checked against the above list of BRefs or fail:

  def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {

    val abRef = for {
      a <- get[A](aRef) // Some function that returns an Option[A]
      abRef = a.bRef
      _ <- validBRefs.find(_ == abRef)
    } yield (abRef)

    abRef match {
      case Some(bRef) => Success(bRef)
      case None => Failure(new MismatchException("No B found matching A's B-ref"))
    }
  }
}

It feels like there should be a way for the final match to be morphed into a map or flatMap or similar construct and incorporated into the preceeding for comprehension.

Also, I would prefer to be able to specify a different failure message if the call to return an Option[A] from the ARef failed (returned None) compared to the BRef check failing (I only care about knowing one reason for the failure, so a scalaz Validation doesn't feel like the ideal fit).

Is this a suitable place to use a monad transformer? If so, does scalaz provide a suitable one, or can someone give an example of what it would look like?

Was it helpful?

Solution 3

If you start out with a Try from the get go with your for-comp then you can eliminate the match at the end. You can do this by forcing the Option to a Try via fold. Here's what that could look like:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {
  for {
    a <- get[A](aRef).fold[Try[A]](Failure[A](new OtherException("Invalid aRef supplied")))(Success(_))
    abRef = a.bRef
    _ <- validBRefs.find(_ == abRef).fold[Try[BRef]](Failure(new MismatchException("No B found matching A's B-ref")))(Success(_))
  } yield abRef
}

With this approach, you can get different exceptions for the two different checks. It's not perfect, but maybe it will work for you.

OTHER TIPS

You can use an implicit conversion

  implicit class OptionOps[A](opt: Option[A]) {

    def toTry(msg: String): Try[A] = {
      opt
        .map(Success(_))
        .getOrElse(Failure(new NoSuchElementException(msg)))
    }
  }

Scala standard lib uses this type of approach. See http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html#companion-objects-of-a-type

Short and simple

Try(option.get)

no need for fancy mapping. In case the option is empty you get an error like:

java.util.NoSuchElementException: None.get
myOption
  .toRight(new Exception("y"))
  .toTry

This code will return either a Success(x) if myOption is Some(x) or Failure(Exception("y")) if it is a None.

If you want to use an Either, you can use Option.toRight:

def getValidBRefForReferencedA(aRef: ARef): Either[Throwable,BRef] = {    
  for {
    a <- get[A](aRef).toRight[Throwable](new Exception("Invalid ARef")).right
    bRef <- validBRefs.find(_ == a.bRef).toRight(new MismatchException("No B found matching A's B-ref")).right
  } yield bRef
}

Using a Try, you can simply write your code in a very procedural way, by throwing appropriate exceptions where needed and wrapping the whole with Try.apply (which will catch the exception and present them as Failure instances).

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = Try {
  val a = get[A](aRef).getOrElse(throw new Exception("Invalid ARef"))
  validBRefs.find(_ == a.bRef).getOrElse(throw new MismatchException("No B found matching A's B-ref"))
}

[Edited to identify different failures]

Tried to simplify

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {
  val abRef = for {
    a <- get[A](aRef)
    bRef = a.bRef
    result = Either.cond(validBRefs.contains(bRef), bRef, "Invalid B Reference")
  } yield result

  abRef.map {
    case Right(bRef) => Success(bRef)
    case Left(error) => Failure(new InvalidReferenceException(error))
  }.getOrElse(Failure(new MismatchException("No B found matching A's B-ref")))
}

I have worked out an alternative solution, although it still doesn't allow me to specify a different failure message for the case that the Option[A] is None versus the BRef not being valid:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = 
  Try {
    (for {
      a <- get[A](aRef)
      bRef = a.bRef
      _ <- bs.find(_ == bRef)
    } yield (bRef)) getOrElse (throw new MismatchException("No B found matching A's B-ref"))
  }

I guess I am expecting it should be possible to have a way to quickly convert the returned Option[A] into a Try (in a suitably idiomatic Scala way - eg. inside a for-comprehension), then continue the processing (obtaining and checking the b-ref) while setting any appropriate Failures along the way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top