I've been trying to tidy up some code which uses multiple functions that all return the type Future[Either[String, A]].
These functions don't compose neatly in a for comprehension because of the problem of having to peak inside the Future and then inside the Either to get the value. After using an EitherT monad transformer I found a solution I liked using EitherT, even though having to add eitherT and having to have the extra step of calling 'run' when you get the final result is not ideal.
My solution is below, but there's one thing I'm not happy with is that you need to create a Monad[Future]
in order for eitherT to work, and this needs an execution context. There's no obvious way to do that. What I've done is to have an implicit execution context in scope of my code and create a Future Monad that I pass the same execution context to so that it both pieces of code use the same one. This seems a little messy, error prone.
Please let me know if there's a better way.
/*
Example of EitherT in ScalaZ
val scalaZVersion = "7.2.8"
"org.scalaz" %% "scalaz-core" % scalaZVersion,
"org.scalaz" %% "scalaz-effect" % scalaZVersion,
*/
import java.util.concurrent.Executors
import scala.concurrent.duration._
import org.scalatest._
import scala.concurrent.{Await, ExecutionContext, Future}
import scalaz.{-\/, Monad, \/, \/-}
import scalaz.EitherT.eitherT
object MonadFutureUtil {
// a Future Monad with a specific instance of an EC
case class MonadWithExecutionContext()(implicit ec : ExecutionContext) extends Monad[Future] {
def point[A](a: => A): Future[A] = Future(a)
def bind[A, B](fa: Future[A])(f: (A) => Future[B]): Future[B] = fa flatMap f
}
}
class TestFutureUtil extends FlatSpec with Matchers with OptionValues with Inside with Inspectors {
implicit val ec = new ExecutionContext {
implicit val threadPool = Executors.newFixedThreadPool(8)
def execute(runnable: Runnable) {
threadPool.submit(runnable)
}
def reportFailure(t: Throwable): Unit = {
println(s"oh no! ${t.getMessage}")
}
}
implicit val monadicEC = MonadFutureUtil.MonadWithExecutionContext()(ec)
// halves the input if it is even else fails
def dummyFunction1(n: Int)(implicit ec : ExecutionContext) : Future[\/[String, Int]] = {
Future.successful(
if(n % 2 == 0)
\/-(n / 2)
else
-\/("An odd number")
)
}
// appends a suffix to the input after converting to a string
// it doesn't like numbers divisible by 3 and 7 though
def dummyFunction2(n: Int)(implicit ec : ExecutionContext) : Future[\/[String, String]] = {
Future.successful(
if(n % 3 != 0 && n % 7 != 0)
\/-(n.toString + " lah!")
else
-\/(s"I don't like the number $n")
)
}
"EitherFuture" should "add the results of two dummyFunction1 calls" in {
val r = for (
rb1 <- eitherT(dummyFunction1(8));
rb2 <- eitherT(dummyFunction1(12))
) yield (rb1 + rb2)
r.run.map {
_ shouldBe \/-(11)
}
}
it should "handle functions with different type" in {
val r = for (
rb1 <- eitherT(dummyFunction1(14));
rb2 <- eitherT(dummyFunction1(12));
rb3 <- eitherT(dummyFunction2(rb2 + rb1))
) yield rb3
val r2 = Await.result(r.run.map {
case \/-(s) =>
(s == "13 lah!")
case -\/(e) =>
false
}, 5 seconds)
assert(r2)
}
it should "doesn't like divisible by 7" in {
val r = for (
rb1 <- eitherT(dummyFunction1(14));
rb2 <- eitherT(dummyFunction1(14));
rb3 <- eitherT(dummyFunction2(rb1 + rb2))
) yield rb3
val r2 = Await.result(r.run.map {
case \/-(s) =>
false
case -\/(e) =>
true
}, 5 seconds)
assert(r2)
}
}
I would suggest trying the following instead of a case class:
This way it should be harder to mix up execution contexts. The proper way would be to use a pure
IO
abstraction, one that doesn't require a execution context to be mapped/flatmapped over...