7

I quite often use the function below to convert Option[Try[_]] to Try[Option[_]] but it feels wrong. Can be such a functionality expressed in more idiomatic way?

def swap[T](optTry: Option[Try[T]]): Try[Option[T]] = {
  optTry match {
    case Some(Success(t)) => Success(Some(t))
    case Some(Failure(e)) => Failure(e)
    case None => Success(None)
  }
}

Say I have two values:

val v1: Int = ???
val v2: Option[Int] = ???

I want to make an operation op (which can fail) on these values and pass that to function f below.

def op(x: Int): Try[String]
def f(x: String, y: Option[String]): Unit

I typically use for comprehension for readability:

for {
  opedV1 <- op(v1)
  opedV2 <- swap(v2.map(op))
} f(opedV1, opedV2)

PS. I'd like to avoid some heavy stuff like scalaz.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
kopiczko
  • 3,018
  • 1
  • 17
  • 24

3 Answers3

8

The cats library allows you to sequence an Option to a Try very easily:

scala> import cats.implicits._
import cats.implicits._

scala> import scala.util.{Failure, Success, Try}
import scala.util.{Failure, Success, Try}

scala> Option(Success(1)).sequence[Try, Int]
res0: scala.util.Try[Option[Int]] = Success(Some(1))

scala> Option(Failure[Int](new IllegalArgumentException("nonpositive integer"))).sequence[Try, Int]
res1: scala.util.Try[Option[Int]] = Failure(java.lang.IllegalArgumentException: nonpositive integer)

scala> None.sequence[Try, Int]
res2: scala.util.Try[Option[Int]] = Success(None)
jub0bs
  • 60,866
  • 25
  • 183
  • 186
4

Sounds like Try { option.map(_.get) } will do what you want.

Dima
  • 39,570
  • 6
  • 44
  • 70
  • 9
    In my view throwing and catching the exception like this has enough overhead (e.g. at runtime but more importantly for readers) that the OPs implementation is preferable, even if it's a little less concise. – Travis Brown Apr 18 '16 at 18:36
  • 2
    What is the overhead of throwing exception (beyond instantiating it and filling in the stack, which would have already been done by now)? I am not aware of it being significantly larger than calling a function. In any event, I figure, if one is concerned with overhead of throwing exceptions, he should not be using `Try` to begin with. Or scala for that matter (there are things in scala that pose a lot more overhead and are a lot harder to avoid than exceptions). Just stick with "C" and gotos to be sure to minimize your overhead :) – Dima Apr 19 '16 at 01:29
  • 1
    That's why I emphasized the overhead for human readers. I often grep for `.get` calls on `Try` or `Option` as a quick measure of the health of a project that's new to me, and I don't make exceptions because the authors are being clever in order to save a couple of lines and avoid a pattern match. :) – Travis Brown Apr 19 '16 at 01:37
  • 2
    Well, that's too bad. I always make exceptions for clever people. I find that they are hard enough to come by to deserve to be treated differently than the stupid ones every now and again ;) – Dima Apr 20 '16 at 00:20
  • Agreed with @Dima. Exception overhead is second to none, comparing to other stuff. What I've learned so far is just: most of the time don't use util.Try; it doesn't play nicely with standard library. – kopiczko Dec 20 '16 at 19:25
  • 1
    @Dima If Josh Bloch's micro benchmark from *Effective Java* is anything to go by, I'd expect that throwing an exception only to catch immediately is significantly less performant than returning it as a value. Regardless of performance, in an expression-oriented language like Scala, manipulating values rather than throwing exceptions is considered more idiomatic. I'm with Travis on this one. – jub0bs Aug 14 '18 at 17:26
1

This variant avoids rethrowing:

import scala.util.{Failure, Success, Try}

def swap[T](optTry: Option[Try[T]]): Try[Option[T]] =
  optTry.map(_.map(Some.apply)).getOrElse(Success(None))

swap(Some(Success(1)))
// res0: scala.util.Try[Option[Int]] = Success(Some(1))

swap(Some(Failure(new IllegalStateException("test"))))
// res1: scala.util.Try[Option[Nothing]] = Failure(java.lang.IllegalStateException: test)

swap(None)
// res2: scala.util.Try[Option[Nothing]] = Success(None)
Aivean
  • 10,692
  • 25
  • 39