Combining EitherT and Future
Asked Answered
Y

2

18

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)
Yetty answered 24/7, 2013 at 21:46 Comment(6)
Are you sure you need the Either business at all? Future can already model failure, and sequencing has the behavior you want.Radioactivate
Hi @TravisBrown I'm still getting my head around scala so you might be right what I need is the ability to capture a backend success, a backend failure, and a future failure and handle them differently. I've now got some code that works, I'll update the orginal question and perhaps that might make it clearer on whether I need a combined EitherT and Future or not.Yetty
One thing that took me a while: Functor[Future] can only be found if an implicit executioncontext is in scopeLegal
Does it work with scala 2.11? I got it working with 2.10 but the error is back now that I've switched to 2.11 (I use "org.typelevel" % "scalaz-contrib-210_2.11" % "0.2").Evelinevelina
@FranckValentin If you use "org.typelevel" %% "scalaz-contrib-210" % "0.2" it should work. Not the double %% and removal of _2.11Soundboard
do we really need scalaz-contrib ? It seems this import is sufficient : import scalaz.std.scalaFuture._Flagelliform
C
6

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.

Cryptomeria answered 25/7, 2013 at 0:40 Comment(1)
By adding "org.typelevel" %% "scalaz-contrib-210" % "0.1.4" to my Build.scala file I was able update my example to get it working, I will update the orginal question with the code.Yetty
D
1

Look at the signature of isRight defined on EitherT:

def isRight(implicit F: Functor[F]): F[Boolean]

It is expecting a Functor parameterized with the type parameter of your EitherT, in your case Future. Scalaz does not provide an implicit Functor for the type Future, you need to write your own following this model:

http://scalaz.github.io/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Functor.scala.html

Notice all the implicit defs for each supported type.

Digitize answered 25/7, 2013 at 0:26 Comment(1)
So if I was using Scalaz 6 it would be as easy as adding implicit def FutureFunctor: Functor[Future] = new Functor[Future] { def fmap[A, B](t: Future[A], f: A => B): Future[B] = t map f } Unfortunately I'm working with Scalaz 7 and the implicit defs seemed to have moved. Where should I be looking in Scalaz7 to do something similiar?Yetty

© 2022 - 2024 — McMap. All rights reserved.