This is the second time I've ran into this problem on a Play/Scala project. I feel like there should be an elegant solution but cannot find it.Let's say we have many services that each return Future[Either[ErrorCase, Success]].In this example, I have serviceA: Future[Either[Error, String]] and serviceB(string: String): Future[Either[Error, String]]serviceB depends on the result of serviceAimport concurrent._import duration._import ExecutionContext.Implicits.globalcase class Error(error: String)def serviceA: Future[Either[Error, String]] = Future(Right("hello"))def serviceB(string: String): Future[Either[Error, String]] = Future(Right(string + "world"))val result: Future[Either[Error, Future[Either[Error, String]]]] = serviceA.map(_.right.map(string => serviceB(string)))// This is painfulval string: Future[String] = result flatMap { eitherA =>eitherA.fold(error => {Future.successful("error: " + error.error)}, futureB => {futureB map { eitherB =>eitherB.fold(error => {"error: " + error.error}, string => {"success: " + string})}})}// Obviously we don't actually blockprintln(Await.result(string, Duration.Inf))The main problem is that we have to handle our error cases separately for each service call. There is no way to map/flatMap over the whole structure and handle all the error cases at once.
It seems that Twitters Try/Future implementation makes it possible to flatMap over a Future[Try] in one go, which I believe would solve the problem above. It does not appear that the Scala/Akka Future and Try implementations act this way though.Is there any better way to compose these Future[Either]'s ?Would using Future[Try]'s open up a path to sane composition?We don't really want to use exceptions because we will then lose the type safety of our error cases. The service methods would no longer define what they will do in the case of an error.Jack Singleton--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://akka.io/faq/
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
Error would be one of our own classes and would vary depending on what went wrong. Basically signify that he could not compute a result, and provide context as to why not.It could signify anything from an external service not being available (more on the exceptional side), to an entity or result set that does not exist.ThanksJack Singleton
It's definitely an option we are weighing.The main thing is that our error cases would no longer be defined in the type signature of the service method.
Using Either's, it's easy to state (and enforce) that serviceA could result in one of a few well defined ServiceAError's, in which the type signature would be Future[Either[ServiceAError, Foo]].
Any client of serviceA now knows that they have to handle any ServiceAError.
In this project that often means an AnticipatedFailure or an UnanticipatedFailure. These are defined as subtypes of a sealed trait so that any non-exhaustive pattern match will be caught at compile time.
We do not consider all of these error cases to be exceptional. In fact, we don't define any of them as Throwable. So having to do so for the sole purpose of using these classes with the Future error handling mechanisms is another reason we are hesitant to go that route.
Jack Singleton
We actually do have our own 'ServiceResult' monad (only it's called 'Outcome'). I've been using Either so there was less overhead in the example.The same problem applies. We end up with a Future[Outcome[F, [Future[Outcome[F, R]]]], and cannot simply flatMap the Future because there is an Outcome standing in the way of the nested Future.
Jack Singleton
That leaves us with a Future[Object] as it is the only common type beween Error and Outcome.Thanks for the quick replies by the way. The answer may in fact be that we cant use futures like this, and should just bite the bullet and use exceptions.Jack Singleton
Yep, I was going to suggest writing monad transformer (s) for this but it felt a bit "over the top".
Cheers, V
So you're basically trying to map / flatMap over a data structure Future, containing a new data structure Either, containing a value T as content for a successful result.The map / flatMap functions will always point to the containing context within the Future, which points to a Either in this case.If you comfortably want to use the functionality (or contexts) of the Future and Either type together, there probably isn't a better solution then to create Monad Transformer like structure combining the two monads into one. Probably (too) complex stuff, but less painful then stair stepping...