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