Let's say you've got a bunch of methods:
def foo() : Try[Seq[String]]
def bar(s:String) : Try[String]
and you want to make a for-comprhension:
for {
list <- foo
item <- list
result <- bar(item)
} yield result
of course this won't compile since Seq cannot be used with Try in this context.
Anyone has a nice solution how to write this clean without breaking it into separate two for's?
I've came across this syntax problem for the thirds time and thought that it's about time to ask about this.
IMHO: Try and Seq is more than what you need to define a monad transformer:
Code for library:
case class trySeq[R](run : Try[Seq[R]]) {
def map[B](f : R => B): trySeq[B] = trySeq(run map { _ map f })
def flatMap[B](f : R => trySeq[B]): trySeq[B] = trySeq {
run match {
case Success(s) => sequence(s map f map { _.run }).map { _.flatten }
case Failure(e) => Failure(e)
}
}
def sequence[R](seq : Seq[Try[R]]): Try[Seq[R]] = {
seq match {
case Success(h) :: tail =>
tail.foldLeft(Try(h :: Nil)) {
case (Success(acc), Success(elem)) => Success(elem :: acc)
case (e : Failure[R], _) => e
case (_, Failure(e)) => Failure(e)
}
case Failure(e) :: _ => Failure(e)
case Nil => Try { Nil }
}
}
}
object trySeq {
def withTry[R](run : Seq[R]): trySeq[R] = new trySeq(Try { run })
def withSeq[R](run : Try[R]): trySeq[R] = new trySeq(run map (_ :: Nil))
implicit def toTrySeqT[R](run : Try[Seq[R]]) = trySeq(run)
implicit def fromTrySeqT[R](trySeqT : trySeq[R]) = trySeqT.run
}
and after you can use for-comrehension (just import your library):
def foo : Try[Seq[String]] = Try { List("hello", "world") }
def bar(s : String) : Try[String] = Try { s + "! " }
val x = for {
item1 <- trySeq { foo }
item2 <- trySeq { foo }
result <- trySeq.withSeq { bar(item2) }
} yield item1 + result
println(x.run)
and it works for:
def foo() = Try { List("hello", throw new IllegalArgumentException()) }
// x = Failure(java.lang.IllegalArgumentException)
You can take advantage of the fact that Try
can be converted to Option
, and Option
to Seq
:
for {
list <- foo.toOption.toSeq // toSeq needed here, as otherwise Option.flatMap will be used, rather than Seq.flatMap
item <- list
result <- bar(item).toOption // toSeq not needed here (but allowed), as it is implicitly converted
} yield result
This will return a (possibly empty, if the Try
s failed) Seq
.
If you want to keep all the exception detail, you'll need a Try[Seq[Try[String]]]
. This can't be done with a single for comprehension, so you're best sticking with plain map
:
foo map {_ map bar}
If you want to mingle your Try
s and Seq
s in a different way, things get fiddlier, as there's no natural way to flatten a Try[Seq[Try[String]]]
. @Yury's answer demonstrates the sort of thing you'd have to do.
Or, if you're only interested in the side effects of your code, you can just do:
for {
list <- foo
item <- list
result <- bar(item)
} result
This works because foreach
has a less restrictive type signature.
A Try can be converted to an Option, which you can than use in a for-comprehension. E.g.
scala> def testIt() = {
| val dividend = Try(Console.readLine("Enter an Int that you'd like to divide:\n").toInt)
| dividend.toOption
| }
testIt: ()Option[Int]
scala> for (x <- testIt()) println (x * x)
Enter an Int that you'd like to divide:
scala> for (x <- testIt()) println (x * x)
Enter an Int that you'd like to divide:
1522756
First time I entered "w", then second time 1234.