brent
brent

Reputation: 1105

Scala - doing something extra in for comprehension on None

Sorry for the bad title

Basically I have a db query that returns None or Some

I want to do a for comprehension over it

val sentMessage = for {
      action <- actionO;
      user <- userO;
      message <- messageModel.findByActionID(action.id)
    } 

But the "extra" something for None; is to do some logging

So (psuedo code)

val sentMessage = for {
      action <- actionO if actionO is undefined Log("We could not find user)

Lift web has the type Box; which has this feature http://simply.liftweb.net/index-7.2.html

id <- S.param("id") ?~ "id param missing" ~> 401

I'm currently using Play - so perhaps there is somthing in there I could leverage?

EDIT - using orElse I now have; can this be cleaned up any more?

 val sentMessage = for {
      action <- findByName(actionName).orElse({Logger.error("Can't find action by name " + actionName); None});
      user <- userModel.findByPk(userID).orElse({Logger.error("Can't find user by id " + userID); None});
      } yield {
      val channels

Thanks

Upvotes: 1

Views: 302

Answers (2)

Ben Reich
Ben Reich

Reputation: 16324

You might consider using an Either is this case. By convention you often hold an error message in Left and the successful value in Right. You can convert an Option to an Either by doing:

val myEither = myOption.toRight("This is an error message")

Then you can use an Either in a for comprehension as:

val transformed = for {
    x <- myEither.right
} yield x * 3

And then you can propagate this error message for as long as you want, and print it when it suits you.

Upvotes: 1

Rich Henry
Rich Henry

Reputation: 1849

You can use a fold to convert an Option to a Try:

intOpt.fold[Try[Int]](Failure(<some exception>))(Success(_))

Then when you're processing your results you just match on the Try, act on the successes, and log on the failures.

// dummy function acting like your db lookup
def sometimesWorks(i: Int): Option[Int] = i match {
  case 1 => Some(i)
  case _ => None
}

// function to convert an Option bearing something to a Try
def asTry[T](in: => Option[T], reason: String): Try[T] = {
  in.fold[Try[T]](Failure(new RuntimeException(reason)))(Success(_))
}

val ints = Vector(0,1,2)
val trys = ints.map((x) => asTry(sometimesWorks(x), "Failed due to sometimesWorks."))

trys.map { t =>
  // Try can be used in the comprehension as well, will propogate errors.
  val result = for {
    x <- t
    y = x * 3
    z = y + 20
  } yield z

  // use a match on the results
  result match {
    case Success(v) => println(s"Worked: $v")
    case Failure(e) => println(s"Logging failure: ${e.getMessage}")
  }
}

Results in:

Logging failure: Failed due to sometimesWorks.
Worked: 23
Logging failure: Failed due to sometimesWorks.

Upvotes: 1

Related Questions