Benjamin Geer
Benjamin Geer

Reputation: 45

Monad transformer in Scala for comprehension to handle Option and collect error messages

I've been looking at a lot of Scala monad transformer examples and haven't been able to figure out how to do what I think is probably something straightforward. I want to write a for comprehension that looks up something in a database (MongoDB), which returns an Option, then if that Option is a Some, looks at its contents and gets another Option, and so on. At each step, if I get a None, I want to abort the whole thing and produce an error message like "X not found". The for comprehension should yield an Either (or something similar), in which a Left contains the error message and a Right contains the successful result of the whole operation (perhaps just a string, or perhaps an object constructed using several of the values obtained along the way).

So far I've just been using the Option monad by itself, as in this trivial example:

val docContentOpt = for {
  doc <- mongoCollection.findOne(MongoDBObject("_id" -> id))
  content <- doc.getAs[String]("content")
} yield content

However, I'm stuck trying to integrate something like Either into this. What I'm looking for is a working code snippet, not just a suggestion to try \/ in Scalaz. I've tried to make sense of Scalaz, but it has very little documentation, and what little there is seems to be written for people who know all about lambda calculus, which I don't.

Upvotes: 2

Views: 488

Answers (2)

maasg
maasg

Reputation: 37435

I'd "try" something like this:

def tryOption[T](option: Option[T], message:String ="" ):Try[T] = option match {
  case Some(v) => Success(v)
  case None => Failure(new Exception(message))
}

val docContentOpt = for {
  doc <- tryOption(mongoCollection.findOne(MongoDBObject("_id" -> id)),s"$id not found")
  content <- tryOption(doc.getAs[String]("content"), "content not found") 
} yield content

Basically an Option to Try conversion that captures the error in an exception. Try is an specialized right-biased Either that is monadic (in contrast to Either, which is not)

Upvotes: 2

Travis Brown
Travis Brown

Reputation: 139038

Try may be what you're looking for, but it's also possible to do this using the "right projection" of the standard library's Either:

val docContentOpt: Either[String, String] = for {
  doc <- mongoCollection.findOne(MongoDBObject("_id" -> id)).toRight(
    s"$id not found"
  ).right
  content <- doc.getAs[String]("content").toRight("Can't get as content").right
} yield content

This may make more sense if your error type doesn't extend Throwable, for example, or if you're stuck on 2.9.2 or earlier (or if you just prefer the generality of Either, etc.).

(As a side note, it'd be nice if the standard library provided toSuccess and toFailure methods on Option that would make converting Option into Try as convenient as converting Option into Either is here—maybe someday.)

(And as another side note, Scalaz doesn't actually buy you much here—it would allow you to write .toRightDisjunction("error") instead of .toRight("error").right, but that's about it. As Gabriel Claramunt points out in a comment, this isn't a case for monad transformers.)

Upvotes: 2

Related Questions