Search code examples
scalatry-catchoption-typescala-2.11

Convert try to option without losing error information in Scala


Introduction

I had created a lovely one-liner:

Option("something").map(_ => Try("something else")).flatten.getOrElse("default")

which actually does not compile, with error:

Error:(15, 31) Cannot prove that scala.util.Try[String] <:< Option[B].
Option("").map(_ => Try("")).flatten.getOrElse("");}
                             ^

so I had found a way around:

Option("something").flatMap(_ => Try("something else").toOption).getOrElse("default")

But, the problem

My colleague warned me, that my construction is actually loosing the error information. This is true, and in real-life application - not acceptable.

After getting rid of all the repetition I had ended up with:

implicit class CoolTry[T](t: Try[T]) extends StrictLogging {
  def toOptionSE: Option[T] = t match {
    case Success(s) => Some(s)
    case Failure(ex) =>
      logger.error(ex.getMessage, ex)
      None
  }
}

using:

Option("something").flatMap(_ => Try(new Exception("error")).toOptionSE).getOrElse("default")

Question

I believe there are many similar cases in every application and I simply do not know if either my approach is bad or Try().toOption is simply done wrong?

I understand that logging is a side effect, but while using Try I guess everyone does expect it if something goes wrong?

  • Can the implicit class be improved?
  • Is there any utility library handling Try().toOption my way?
  • Or what (other) approach should I take here?

Thanks!


Solution

  • Forcing logging every time you change a Try[T] to an Option[T] is an undesired effect IMO. When you do such a transformation you're explicitly admitting that you don't really care about the internals of the failure, if it happened. All you want is to access the result, if it exists. Most of the time you say "well, this is undesirable, I always want to log exceptions", but sometimes you simply only care about the end result, so dealing with an Option[T] can be good enough.

    Or what (other) approach should I take here?

    Using Either[A, B] is an option here, especially in Scala 2.12 when it became right biased. You can simply map over it and only at the end check if there was an error and then log (created with Scala 2.12.1):

    val res: Either[Throwable, String] = Option("something")
      .map(_ => Try("something else").toEither)
      .getOrElse(Right("default"))
      .map(str => s"I got my awesome $str")
    

    Ideally (IMO) deferring the logging side effect to the last possible point would serve you better.