Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining EitherT and Future

I have an app that does a lot of calls to different backend systems, and hoping to use for-comprehensions to simplify the process flow across the backend systems.

I'm looking to combine EitherT (scalaz) and Future (scala 2.10) so I can capture the first potential error (where its a future or backend system issue) and return an appropriate message to the end user. I've had a quick look a scalaz Validation but the recommendation for capturing first error and not all errors is to use EitherT.

I'm trying a simple example in REPL first, however I'm getting the following error

error: could not find implicit value for parameter F: scalaz.Functor[scala.concurrent.Future]

import scala.concurrent._
import scalaz._
import Scalaz._
import ExecutionContext.Implicits.global

type EitherFuture[+A] = EitherT[Future, String, A]

def method1Success : EitherFuture[Int] = {
  println("method 1 success")
  EitherT {
    Future {
      1.right
    }
  }
}

def method2Failure : EitherFuture[Int] = {
  println("method 2 failure")
  EitherT {
    Future {
      "fail".left
    }
  }
}

val m1 = method1Success

// problem
m1.isRight

// problem
def methodChain1 = {
  for {
    a <- method1Success
    b <- method2Failure
  } yield b
}

I'm still new to both scala and scalaz so any pointers would be great.

** Update **

By including scalaz-contrib based on @stew suggestion I now have an updated version that shows for-comprehensions with the combined EitherT and Future showing different simple use cases backend success, a backend failure, and a future failure

import scala.concurrent._
import scalaz._
import Scalaz._
import ExecutionContext.Implicits.global
import scalaz.contrib._
import scalaz.contrib.std._
import scala.concurrent.duration._

type EitherFuture[+A] = EitherT[Future, String, A]

// various methods that mimic success or different failures
def methodBackendSuccess : EitherFuture[Int] = {
  println("method backend success")
  EitherT {
    Future {1.right}
  }
}

def methodBackendFailure : EitherFuture[Int] = {
  println("method backend failure")
  EitherT {
    Future { "fail".left}
  }
}

def methodFutureFailure : EitherFuture[Int]  = {
  println("method future failure")
  EitherT {
    Future.failed(new Exception("future failed"))
  }
}

// different combinations for for-comprehensions
def methodChainBackendSuccess = {
  for {
    a <- methodBackendSuccess
    b <- methodBackendSuccess
    c <- methodBackendSuccess
  } yield c
}

def methodChainBackendFailure = {
  for {
    a <- methodBackendSuccess
    b <- methodBackendFailure
    c <- methodBackendSuccess
  } yield c
}

def methodChainFutureFailure = {
  for {
    a <- methodBackendSuccess
    b <- methodFutureFailure
    c <- methodBackendSuccess
  } yield c
}

// process results for different chain methods
def processOutcome(chainMethod: => EitherFuture[Int]):Int = try {
    val x = Await.result(chainMethod.run, 30 seconds) 
    x.toEither match {                             
      case Left(l) => {
        println("Backend failure <" + l + ">")
        -1 
      }
      case Right(r) => {
        println("Backend success <" + r + ">") 
        r
      }
    }
  } catch {
    case e: Exception => {
      println("Future error <" + e.getMessage + ">" )
      -99
  }
}

// run tests
val backendSuccess = processOutcome(methodChainBackendSuccess)
val backendFailure = processOutcome(methodChainBackendFailure)
val futureFailure = processOutcome(methodChainFutureFailure)
like image 631
Mark Sivill Avatar asked Jul 24 '13 21:07

Mark Sivill


1 Answers

You need to either import or provide a Functor instance for Future. I recommend using the one from the scalaz-contrib project. -contrib is a separate project worked on by the same people that work on scalaz. The Future instances are in this package instead of scalaz-core because scalaz-core, for now, maintains compatibility between scala 2.9 and 2.10.

like image 157
stew Avatar answered Oct 14 '22 12:10

stew