Synesso
Synesso

Reputation: 38978

Why can't I flatMap a Try?

Given

val strings = Set("Hi", "there", "friend")
def numberOfCharsDiv2(s: String) = scala.util.Try { 
  if (s.length % 2 == 0) s.length / 2 else throw new RuntimeException("grr") 
}

Why can't I flatMap away the Try resulting from the method call? i.e.

strings.flatMap(numberOfCharsDiv2)
<console>:10: error: type mismatch;
 found   : scala.util.Try[Int]
 required: scala.collection.GenTraversableOnce[?]
              strings.flatMap(numberOfCharsDiv2)

or

for {
  s <- strings
  n <- numberOfCharsDiv2(s)
} yield n
<console>:12: error: type mismatch;
 found   : scala.util.Try[Int]
 required: scala.collection.GenTraversableOnce[?]
            n <- numberOfCharsDiv2(s)

However if I use Option instead of Try there's no problem.

def numberOfCharsDiv2(s: String) = if (s.length % 2 == 0) 
  Some(s.length / 2) else None
strings.flatMap(numberOfCharsDiv2) # =>  Set(1, 3)

What's the rationale behind not allowing flatMap on Try?

Upvotes: 14

Views: 5086

Answers (4)

Jack Davidson
Jack Davidson

Reputation: 4933

Kigyo explains well why Scala does not do this implicitly. To put it simply, Scala does not want to automatically throw away the exception that is preserved in a Try.

Scala does provide a simple way to explicitly translate a Try into an Option though. This is how you can use a Try in a flatmap:

strings.flatMap(numberOfCharsDiv2(_).toOption)

Upvotes: 2

Vlad Patryshev
Vlad Patryshev

Reputation: 1422

It is a Monad in Scala 2.11:

scala> import scala.util._ 
import scala.util._

scala> val x: Try[String] = Success[String]("abc")
x: scala.util.Try[String] = Success(abc)

scala> val y: Try[String] = Failure[String](new Exception("oops"))
y: scala.util.Try[String] = Failure(java.lang.Exception: oops)

scala> val z = Try(x)
z: scala.util.Try[scala.util.Try[String]] = Success(Success(abc))

scala> val t = Try(y)
t: scala.util.Try[scala.util.Try[String]] = Success(Failure(java.lang.Exception: oops))

scala> z.flatten
res2: scala.util.Try[String] = Success(abc)

scala> t.flatten
res3: scala.util.Try[String] = 
Failure(java.lang.UnsupportedOperationException: oops)

Upvotes: 2

Kigyo
Kigyo

Reputation: 5768

Let's look at the signature of flatMap.

def flatMap[B](f: (A) => GenTraversableOnce[B]): Set[B]

Your numberOfCharsDiv2 is seen as String => Try[Int]. Try is not a subclass of GenTraversableOnce and that is why you get the error. You don't strictly need a function that gives a Set only because you use flatMap on a Set. The function basically has to return any kind of collection.

So why does it work with Option? Option is also not a subclass of GenTraversableOnce, but there exists an implicit conversion inside the Option companion object, that transforms it into a List.

implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList

Then one question remains. Why not have an implicit conversion for Try as well? Because you will probably not get what you want.

flatMap can be seen as a map followed by a flatten.

Imagine you have a List[Option[Int]] like List(Some(1), None, Some(2)). Then flatten will give you List(1,2) of type List[Int].

Now look at an example with Try. List(Success(1), Failure(exception), Success(2)) of type List[Try[Int]].

How will flatten work with the failure now?

  • Should it disappear like None? Then why not work directly with Option?
  • Should it be included in the result? Then it would be List(1, exception, 2). The problem here is that the type is List[Any], because you have to find a common super class for Int and Throwable. You lose the type.

These should be reasons why there isn't an implicit conversion. Of course you are free to define one yourself, if you accept the above consequences.

Upvotes: 12

bobbyr
bobbyr

Reputation: 326

The problem is that in your example, you're not flatmapping over Try. The flatmap you are doing is over Set.

Flatmap over Set takes a Set[A], and a function from A to Set[B]. As Kigyo points out in his comment below this isn't the actual type signature of flatmap on Set in Scala, but the general form of flat map is:

M[A] => (A => M[B]) => M[B]

That is, it takes some higher-kinded type, along with a function that operates on elements of the type in that higher-kinded type, and it gives you back the same higher-kinded type with the mapped elements.

In your case, this means that for each element of your Set, flatmap expects a call to a function that takes a String, and returns a Set of some type B which could be String (or could be anything else).

Your function

numberOfCharsDiv2(s: String)

correctly takes a String, but incorrectly returns a Try, rather then another Set as flatmap requires.

Your code would work if you used 'map', as that allows you to take some structure - in this case Set and run a function over each element transforming it from an A to a B without the function's return type conforming to the enclosing structure i.e. returning a Set

strings.map(numberOfCharsDiv2)

res2: scala.collection.immutable.Set[scala.util.Try[Int]] = Set(Success(1), Failure(java.lang.RuntimeException: grr), Success(3))

Upvotes: 6

Related Questions