2

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.

  • 2
    Don't know if you are aware but Scala 2.10 has the Try monad for handling success and failure: http://www.scala-lang.org/api/current/#scala.util.Try – reggoodwin May 22 '14 at 14:00
  • 1
    Since you're dealing with the same monad (Option) Monad transformers is not what are you looking for, Monad transformers are to compose different type of monads (e.g. List[Option], Future[Try], etc. ) – GClaramunt May 22 '14 at 14:57
  • @GClaramunt Ah, OK. I assumed I needed an Option monad plus an Either monad, hence the need for a monad transformer. Thanks for clarifying that. – Benjamin Geer May 22 '14 at 20:55

2 Answers2

2

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)

maasg
  • 37,100
  • 11
  • 88
  • 115
  • You could replace `tryOption` by the `fold` method of `Option`, as explained e.g. here: http://stackoverflow.com/a/17526264/1805388 – ValarDohaeris May 22 '14 at 14:29
  • @ValarDohaeris I find catamorphisms on Option very unreadable as they always start with Failure: "Guilty until proven innocent". But it's indeed more concise. – maasg May 22 '14 at 14:33
2

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.)

Travis Brown
  • 138,631
  • 12
  • 375
  • 680