I am trying to find a cleaner way to express code that looks similar to this:
def method1: Try[Option[String]] = ???
def method2: Try[Option[String]] = ???
def method3: Try[Option[String]] = ???
method1 match
{
case f: Failure[Option[String]] => f
case Success(None) =>
method2 match
{
case f:Failure[Option[String]] => f
case Success(None) =>
{
method3
}
case s: Success[Option[String]] => s
}
case s: Success[Option[String]] => s
}
As you can see, this tries each method in sequence and if one fails then execution stops and the base match resolves to that failure. If method1 or method2 succeeds but contains None then the next method in the sequence is tried. If execution gets to method3 its results are always returned regardless of Success or Failure. This works fine in code but I find it difficult to follow whats happening.
I would love to use a for comprehension
for
{
attempt1 <- method1
attempt2 <- method2
attempt3 <- method3
}
yield
{
List(attempt1, attempt2, attempt3).find(_.isDefined)
}
because its beautiful and what its doing is quite clear. However, if all methods succeed then they are all executed every time, regardless of whether an earlier method returns a usable answer. Unfortunately I can't have that.
Any suggestions would be appreciated.
scalaz can be of help here. You'll need scalaz-contrib which adds a monad instance for Try, then you can use OptionT which has nice combinators. Here is an example:
import scalaz.OptionT
import scalaz.contrib.std.utilTry._
import scala.util.Try
def method1: OptionT[Try, String] = OptionT(Try(Some("method1")))
def method2: OptionT[Try, String] = OptionT(Try(Some("method2")))
def method3: OptionT[Try, String] = { println("method 3 is never called") ; OptionT(Try(Some("method3"))) }
def method4: OptionT[Try, String] = OptionT(Try(None))
def method5: OptionT[Try, String] = OptionT(Try(throw new Exception("fail")))
println((method1 orElse method2 orElse method3).run) // Success(Some(method1))
println((method4 orElse method2 orElse method3).run) // Success(Some(method2))
println((method5 orElse method2 orElse method3).run) // Failure(java.lang.Exception: fail)
If you don't mind creating a function for each method, you can do the following:
(Try(None: Option[String]) /: Seq(method1 _, method2 _, method3 _)){ (l,r) =>
l match { case Success(None) => r(); case _ => l }
}
This is not at all idiomatic, but I would like to point out that there's a reasonably short imperative version also with a couple tiny methods:
def okay(tos: Try[Option[String]]) = tos.isFailure || tos.success.isDefined
val ans = {
var m = method1
if (okay(m)) m
else if ({m = method2; okay(m)}) m
method3
}
The foo method should do the same stuff as your code, I don't think it is possible to do it using the for comprehension
type tryOpt = Try[Option[String]]
def foo(m1: tryOpt, m2: tryOpt, m3: tryOpt) = m1 flatMap {
case x: Some[String] => Try(x)
case None => m2 flatMap {
case y: Some[String] => Try(y)
case None => m3
}
}
method1.flatMap(_.map(Success _).getOrElse(method2)).flatMap(_.map(Success _).getOrElse(method3))
How this works:
The first flatMap takes a Try[Option[String]], if it is a Failure it returns the Failure, if it is a Success it returns _.map(Success _).getOrElse(method2) on the option. If the option is Some then it returns the a Success of the Some, if it is None it returns the result of method2, which could be Success[None], Success[Some[String]] or Failure.
The second map works similarly with the result it gets, which could be from method1 or method2.
Since getOrElse takes a by-name paramater method2 and method3 are only called if they need to be.
You could also use fold instead of map and getOrElse, although in my opinion that is less clear.
From this blog:
def riskyCodeInvoked(input: String): Int = ???
def anotherRiskyMethod(firstOutput: Int): String = ???
def yetAnotherRiskyMethod(secondOutput: String): Try[String] = ???
val result: Try[String] = Try(riskyCodeInvoked("Exception Expected in certain cases"))
.map(anotherRiskyMethod(_))
.flatMap(yetAnotherRiskyMethod(_))
result match {
case Success(res) => info("Operation Was successful")
case Failure(ex: ArithmeticException) => error("ArithmeticException occurred", ex)
case Failure(ex) => error("Some Exception occurred", ex)
}
BTW, IMO, Option is no need here?
Related
I have a function like this:
def foo(item: Item) : Option[Int] = Try{
// Some code that can blow up
}.toOption
I have a list of items and I want to map through them, and apply the above function. But if the function above blows up and returns a None then the result of the map should be an error:
items.map{
item => foo(item)
}
Is map not the right thing to do here? It doesn't seem like it
This is called traverse. If you can use cats, it is as simple as:
import cats.implicits._
val result = items.traverse(foo) // Option[List[Int]]
If not, you can implement it pretty easily:
def traverse[A, B](data: List[A])(f: A => Option[B]): Option[List[B]] = {
#annotation.tailrec
def loop(remaining: List[A], acc: List[B]): Option[List[B]] =
remaining match {
case a :: as => f(a) match {
case Some(b) => loop(remaining = as, b :: acc)
case None => None
}
case Nil => Some(acc.reverse)
}
loop(remaining = data, acc = List.empty)
}
Which you can use like:
val result = traverse(items)(foo) // Option[List[Int]]
(however, I would suggest you to use cats instead, since it is more general).
For out-of-the-box short-circuiting, consider wrapping the list-mapping with Try like so
def fooUnsafe(item: Item): Int = // might throw
Try(items.map(fooUnsafe))
If you wish to keep def foo(item: Item) : Option[Int] signature then the following will also short-circuit
Try(list.map(v => foo(v).get))
Scala: I need to perform an action only if the previous future returned Some(x). what's a better way to do this than using below code
def tryThis: Future[Option[T]] = {...}
val filteredFuture = tryThis.filter(_.exists(_ => true))
def abc = filteredFuture.map( _ => {...})
The best way is to call map on the Option like this:
tryThis.map(_.map(_ => {...}))
This will call the function only if the Future returns Some(x). The result with be another Future[Option[U]] where U is the result of your function.
Note that this will return Future(None) if the the original Option was None, whereas filter will generate a failed exception, so they don't quite do the same thing.
def tryThis: Future[Option[T]] = {...}
// Resulting future will be failed if it a None
// and its type will be that of the expression in `x…`
def abc = tryThis collect { case Some(x) => x… }
// Resulting future will be a None if it was a None
// and a Some with the type of the expression in `x…`
def abc = tryThis map { _.map(x => x…) }
You could replace:
tryThis.filter(_.exists(_ => true))
with:
tryThis.filter(_.isDefined)
import scala.concurrent.ExecutionContext.Implicits.global
def square(a: Int): Future[Option[Int]] = Future.successful(Option(a * a))
def printResult(a: Int): Unit = println(s"Result: $a")
square(2).foreach(_.map(printResult))
EDIT: As per #Thilo suggestion
I want to update a sequence in Scala, I have this code :
def update(userId: Long): Either[String, Int] = {
Logins.findByUserId(userId) map {
logins: Login => update(login.id,
Seq(NamedParameter("random_date", "prefix-" + logins.randomDate)))
} match {
case sequence : Seq(Nil, Int) => sequence.foldLeft(Right(_) + Right(_))
case _ => Left("error.logins.update")
}
}
Where findByUserId returns a Seq[Logins] and update returns Either[String, Int] where Int is the number of updated rows,
and String would be the description of the error.
What I want to achieve is to return an String if while updating the list an error happenes or an Int with the total number of updated rows.
The code is not working, I think I should do something different in the match, I don't know how I can check if every element in the Seq of Eithers is a Right value.
If you are open to using Scalaz or Cats you can use traverse. An example using Scalaz :
import scalaz.std.either._
import scalaz.std.list._
import scalaz.syntax.traverse._
val logins = Seq(1, 2, 3)
val updateRight: Int => Either[String, Int] = Right(_)
val updateLeft: Int => Either[String, Int] = _ => Left("kaboom")
logins.toList.traverseU(updateLeft).map(_.sum) // Left(kaboom)
logins.toList.traverseU(updateRight).map(_.sum) // Right(6)
Traversing over the logins gives us a Either[String, List[Int]], if we get the sum of the List we get the wanted Either[String, Int].
We use toList because there is no Traverse instance for Seq.
traverse is a combination of map and sequence.
We use traverseU instead of traverse because it infers some of the types for us (otherwise we should have introduced a type alias or a type lambda).
Because we imported scalaz.std.either._ we can use map directly without using a right projection (.right.map).
You shouldn't really use a fold if you want to exit early. A better solution would be to recursively iterate over the list, updating and counting successes, then return the error when you encounter one.
Here's a little example function that shows the technique. You would probably want to modify this to do the update on each login instead of just counting.
val noErrors = List[Either[String,Int]](Right(10), Right(12))
val hasError = List[Either[String,Int]](Right(10), Left("oops"), Right(12))
def checkList(l: List[Either[String,Int]], goodCount: Int): Either[String, Int] = {
l match {
case Left(err) :: xs =>
Left(err)
case Right(_) :: xs =>
checkList(xs, (goodCount + 1))
case Nil =>
Right(goodCount)
}
}
val r1 = checkList(noErrors, 0)
val r2 = checkList(hasError, 0)
// r1: Either[String,Int] = Right(2)
// r2: Either[String,Int] = Left(oops)
You want to stop as soon as an update fails, don't you?
That means that you want to be doing your matching inside the map, not outside. Try is actually a more suitable construct for this purpose, than Either. Something like this, perhaps:
def update(userId: Long): Either[String, Int] = Try {
Logins.findByUserId(userId) map { login =>
update(login.id, whatever) match {
case Right(x) => x
case Left(s) => throw new Exception(s)
}
}.sum
}
.map { n => Right(n) }
.recover { case ex => Left(ex.getMessage) }
BTW, a not-too-widely-known fact about scala is that putting a return statement inside a lambda, actually returns from the enclosing method. So, another, somewhat shorter way to write this would be like this:
def update(userId: Long): Either[String, Int] =
Logins.findByUserId(userId).foldLeft(Right(0)) { (sum,login) =>
update(login.id, whatever) match {
case Right(x) => Right(sum.right + x)
case error#Left(s) => return error
}
}
Also, why in the world does findUserById return a sequence???
I've been trying to simplify the way I do futures in Scala. I got at one point a Future[Option[Future[Option[Boolean]] but I've simplified it further below. Is there a better way to simplify this?
Passing a future of "failed" doesn't seem like the best way to do this. i.e. in the sequential world I simply returned "FAIL!!" any time it failed rather than continuing to the end. Are there other ways?
val doSimpleWork = Future {
//Do any arbitrary work (can be a different function)
true //or false
}
val doComplexWork = Future {
//Do any arbitrary work (can be a different function)
Some("result") //or false
}
val failed = Future {
//Do no work at all!!! Just return
false
}
val fut1 = doSimpleWork
val fut2 = doSimpleWork
val fut3 = (fut1 zip fut2).map({
case (true, true) => true
case _ => false
})
val fut4 = fut3.flatMap({
case true =>
doComplexWork.flatMap({
case Some("result") =>
doSimpleWork
case None =>
failed
})
case false =>
failed
})
fut4.map({
case true =>
"SUCCESS!!!"
case _ =>
"FAIL!!"
})
Note that in your example, because you're eagerly instantiating the Futures to a val, all of them will start executing as soon as you declare them (val x = Future {...}). Using methods instead will make the Futures execute only when they're requested by the chain of execution.
One way to avoid the further computation would be to throw an exception, then handle it with onFailure:
def one = future { println("one") ; Some(1) }
def two = future { println("two") ; throw new Exception("no!"); 2 }
def three = future { println("three") ; 3 }
val f = one flatMap {
result1 => two flatMap {
result2 => three
}
}
f onFailure {
case e: Exception =>
println("failed somewhere in the chain")
}
You can see here that "three" isn't supposed to be printed out, because we fail on two. This is the case:
one
two
failed somewhere in the chain
a "Monad transformer" is a construct which lets you combine the "effects" of two monads, the scalaz project provides several different monad transformers. My suggestion is that you can use the OptionT monad transformer to simplify your code if you also make use of the fact that Option[Unit] is isomorphic to Boolean (Some(()) == true and None == false). Here's a complete example:
import scalaz._
import Scalaz._
import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.concurrent.duration._
object Foo {
// We need a Monad instance for Future, here is a valid one, or you can use the implementation
// in the scalaz-contrib project, see http://typelevel.org
implicit def futureMonad(implicit executor: ExecutionContext): Monad[Future] = new Monad[Future] {
override def bind[A, B](fa: Future[A])(f: A ⇒ Future[B]) = fa flatMap f
override def point[A](a: ⇒ A) = Future(a)
override def map[A, B](fa: Future[A])(f: A ⇒ B) = fa map f
}
// OptionT allows you to combine the effects of the Future and Option monads
// to more easily work with a Future[Option[A]]
val doSimpleWork : OptionT[Future,Unit] = OptionT(Future {
// Option[Unit] is isomorphic to Boolean
Some(()) //or None
})
val simpleFail : OptionT[Future,Unit] = OptionT(Future {
None
})
val doComplexWork: OptionT[Future,String] = OptionT(Future {
Some("result") //or None
})
val f1 = doSimpleWork
val f2 = doSimpleWork
val f3 = doComplexWork
val f4 = doSimpleWork
def main(argv: Array[String]) {
val result = for {
_ <- f1
// we don't get here unless both the future succeeded and the result was Some
_ <- f2
_ <- f3
r <- f4
} yield(r)
result.fold((_ => println("SUCCESS!!")),println("FAIL!!"))
// "run" will get you to the Future inside the OptionT
Await.result(result.run, 1 second)
}
}
You could try something like this, using for comprehensions to clean up the code a bit:
def doSimpleWork = Future{
//do some simple work
true
}
def doComplexWork = Future{
//do something complex here
Some("result")
}
val fut1 = doSimpleWork
val fut2 = doSimpleWork
val fut = for{
f1Result <- fut1
f2Result <- fut2
if (f1Result && f2Result)
f3Result <- doComplexWork
if (f3Result.isDefined)
f4Result <- doSimpleWork
} yield "success"
fut onComplete{
case Success(value) => println("I succeeded")
case Failure(ex) => println("I failed: " + ex.getMessage)
}
And if you really just wanted to print out "success" or "failed" at the end, you could change that last piece of code to:
fut.recover{case ex => "failed"} onSuccess{
case value => println(value)
}
Now, to explain what's going on. For starters, we've defined two functions that return Futures that are doing some async work. The doSimpleWork function will do some simple work and return a boolean success/fail indicator. The doComplexWork function will do something more complex (and time consuming) and return an Option[String] representing a result. We then kick off two parallel invocations of doSimpleWork before entering the for comprehension. In for for comp, we get the results of fut1 and fut2 (in that order) before checking if they were both successful. If not, we would stop here, and the fut val would be failed with a NoSuchElementException which is what you get when a condition like this fails in a for comp. If both were successful, we would continue on and invoke the doComplexWork function and wait for its result. We would then check its result and if it was Some, we would kick off one last invocation of doSimpleWork. If that succeeds, we would yield the string "success". If you check the type of the fut val, its of type Future[String].
From there, we use one of the async callback functions to check if the whole sequence of calls either made it all the way through (the Success case), or failed somewhere in the process (the Failure case), printing out something related to which case it hit. In the alternate final code block, we recover from any possible failure by returning the String "failed" "and then use just the onSuccess callback which will print either "success" or "failed" depending on what happened.
The traverse method from Future object stops at first failure. I want a tolerant/forgiving version of this method which on occurrence of errors carries on with the rest of the sequence.
Currently we have added the following method to our utils:
def traverseFilteringErrors[A, B <: AnyRef]
(seq: Seq[A])
(f: A => Future[B]): Future[Seq[B]] = {
val sentinelValue = null.asInstanceOf[B]
val allResults = Future.traverse(seq) { x =>
f(x) recover { case _ => sentinelValue }
}
val successfulResults = allResults map { result =>
result.filterNot(_ == sentinelValue)
}
successfulResults
}
Is there a better way to do this?
A genuinely useful thing (generally speaking) would be to be able to promote the error of a future into a proper value. Or in other words, transform a Future[T] into a Future[Try[T]] (the succesful return value becomes a Success[T] while the failure case becomes a Failure[T]). Here is how we might implement it:
// Can also be done more concisely (but less efficiently) as:
// f.map(Success(_)).recover{ case t: Throwable => Failure( t ) }
// NOTE: you might also want to move this into an enrichment class
def mapValue[T]( f: Future[T] ): Future[Try[T]] = {
val prom = Promise[Try[T]]()
f onComplete prom.success
prom.future
}
Now, if you do the following:
Future.traverse(seq)( f andThen mapValue )
You'll obtain a succesful Future[Seq[Try[A]]], whose eventual value contains a Success instance for each successful future, and a Failure instance for each failed future.
If needed, you can then use collect on this seq to drop the Failure instances and keep only the sucessful values.
In other words, you can rewrite your helper method as follows:
def traverseFilteringErrors[A, B](seq: Seq[A])(f: A => Future[B]): Future[Seq[B]] = {
Future.traverse( seq )( f andThen mapValue ) map ( _ collect{ case Success( x ) => x } )
}