结合期货(Twitter)和Scala

结合期货(Twitter)和Scala,scala,exception,either,Scala,Exception,Either,我们使用Twitter期货(作为欺骗堆栈的一部分),我不喜欢使用(业务)异常来控制应用程序流的概念,因为异常不会显示在方法签名中 所以我想用Future[或者[A,B]]作为替代品 但我在用这个概念来理解期货时遇到了一些问题: 例如,我们有一个存储库方法: def getUserCredentialsByNickname(nickname: String): Future[Either[EntityNotFound, UserCredentials]] 还有一个处理程序方法,它使用这个repo

我们使用Twitter期货(作为欺骗堆栈的一部分),我不喜欢使用(业务)异常来控制应用程序流的概念,因为异常不会显示在方法签名中

所以我想用Future[或者[A,B]]作为替代品

但我在用这个概念来理解期货时遇到了一些问题:

例如,我们有一个存储库方法:

def getUserCredentialsByNickname(nickname: String): Future[Either[EntityNotFound, UserCredentials]]
还有一个处理程序方法,它使用这个repo并执行一些其他检查,还创建一个令牌

def process(request: LoginRequest): Future[Either[Failure, Login]] = {
      for {
        credentialsEither <- userRepository.getUserCredentialsByNickname(request.username)
        ...several other calls/checks which should 'interrupt' this for comprehension
        token <- determineToken(credentials)
} yield token
def进程(请求:LoginRequest):未来[失败,登录]={
为了{

credentials或您可以通过隐式添加一个处理
的方法来扩展Future类,而不必每次自己匹配它:

implicit class EitherHandlingFuture[Exception, Value](future: Future[Either[Exception, Value]]) {
  def mp[Return](fn: Value => Return) = {
    future.map { eth: Either[Exception, Value] =>
      eth match {
        case Left(ex: Exception) => { print("logging the exception") /* handle or rethrow */ }
        case Right(res: Value) => fn(res)
      }
    }
  }
}
那么,这将是可能的:

def someComputation: Future[Either[Exception, Int]] = Future.value(Right(3))

someComputation mp { res: Int =>
  println(res)
}

请注意,上面的代码片段不适用于理解的
,因为要支持它们,必须完全实现map/flatMap。为此,您可能希望对
Future

进行子类化,因此现在我尝试使用Scalaz(与中性scala相比,这是右倾的)还有Monad变形器,它似乎正是我想要的。多亏了Huw,尤其是Lars Hupel,为我指明了正确的方向

以下是Twitter futures和Scalaz以及Either的示例:

import com.twitter.util.{Await, Future}
import scalaz.{Monad, Functor, EitherT, \/}
import scalaz.syntax.ToIdOps

object EitherTest extends App with ToIdOps{

  // make Twitter futures work with EitherT
  implicit val FutureFunctor = new Functor[Future] {
    def map[A, B](a: Future[A])(f: A => B): Future[B] = a map f
  }
  implicit val FutureMonad = new 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
  }

  // The example begins here:

  case class InvalidInfo(error: String)
  case class Response(msg: String)


  class ComponentA {
    def foo(fail: Boolean): Future[\/[InvalidInfo, Response]] = {
      if(fail) Future(InvalidInfo("Error A").left) else Future(Response("ComponentA Success").right)
    }
  }
  class ComponentB {
    def bar(fail: Boolean): Future[\/[InvalidInfo, Response]] = {
      if(fail) Future(InvalidInfo("Error B").left) else Future(Response("ComponentB Success").right)
    }
  }

  val a = new ComponentA
  val b = new ComponentB

  val result = for {
    resultA <- EitherT(a.foo(false))
    resultB <- EitherT(b.bar(false))
  } yield (resultA, resultB)

  println(Await.result(result.run))
}
import com.twitter.util.{wait,Future}
导入scalaz.{Monad,函子,EitherT,\/}
导入scalaz.syntax.ToIdOps
对象EitherTest使用ToIdOps扩展应用程序{
//让Twitter期货与EitherT合作
隐式val FutureFunctor=新函子[未来]{
定义映射[A,B](A:Future[A])(f:A=>B):Future[B]=A映射f
}
隐式val FutureMonad=新单子[Future]{
定义点[A](A:=>A):未来[A]=未来(A)
def bind[A,B](fa:Future[A])(f:(A)=>Future[B]):Future[B]=fa flatMap f
}
//示例从这里开始:
案例类InvalidInfo(错误:字符串)
案例类响应(消息:字符串)
类组件A{
def foo(失败:布尔):未来[\/[InvalidInfo,响应]]={
if(失败)Future(无效信息(“错误A”)。左侧)else Future(响应(“组件A成功”)。右侧)
}
}
类组件B{
定义栏(失败:布尔):未来[\/[InvalidInfo,响应]]={
if(fail)Future(InvalidInfo(“错误B”)。左侧)else Future(响应(“组件B成功”)。右侧)
}
}
val a=新组件a
val b=新组件b
val结果=用于{

结果也许使用
Try
数据结构更好,它已经与欺诈期货相集成:我也看过Try,但它的缺点是事实(据我所知),它不会在方法的签名中指定确切的错误情况,也不会在您使用它时指定它的类型。使用上面的签名
Future[EntityNotFound,UserCredentials]]
我立即看到了可以返回的失败类型。当我使用try时,我必须查看处理try的代码的实现。我认为使用Scalaz的/基于EitherT/ValidationT的解决方案会比运行子类化更好。@Huw:我根本不知道Scalaz。你有我的用例的例子吗?我在这里这是Scalaz适合使用的用例的一个子集。从开发人员的生产力和可读性方面来说,这也是一项巨大的投资。它在某种程度上解决了问题,但这并不意味着它是一个明确的解决方案。@Huw请参阅下面我自己关于Scalaz的工作示例的答案。