结合不同的容器"在猫XorT

时间:2016-07-17 10:43:18

标签: scala functional-programming monad-transformers scala-cats

例如,我们有一些服务使用不同的"容器" FutureOption

//first service with Future
class FirstService { 
      getData(): XorT[Future, ServiceError, SomeData]
}

//second service with Optin
class SecondService {
      getData(): XorT[Option, ServiceError, SomeData]
}

我们如何将它们组合起来使用一个来理解以避免类型不匹配?

val result = for {
    data1 <- firstService.getData()
    data2 <- secondService.getData() // type mismatch required XorT[Future, ServiceError, SomeData]
} yield mergeResult(data1, data2)

2 个答案:

答案 0 :(得分:2)

XorT[F, A, B]只是F[A Xor B]上方便的包装器,因此您基本上要问的是:如何合并FutureOption。因为您仍然需要以某种形式返回Future,这主要是:如何处理Option

有几种可能性:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.data.XorT
import cats.implicits._

type ServiceError = String
type FutureErrorOr[A] = XorT[Future, ServiceError, A]

val fXorT: FutureErrorOr[Int] = XorT.right(Future.successful(1))
val oXorT: XorT[Option, ServiceError, Int] = XorT.right(1.some)
  • Option变为FutureNoneFuture.failed):

    val opt2fut: FutureErrorOr[Int] = 
      XorT(oXorT.value.fold(
        Future.failed[ServiceError Xor Int](new NoSuchElementException())(
        Future.successful _))
    
    for { a <- fXort; b <- opt2fut } yield a + b
    
  • Option变为ServiceError Xor ?NoneXor.Left):

    val opt2xor: FutureErrorOr[Int] = 
      XorT.fromXor[Future](oXorT.value.getOrElse("no elem".left))
    
    for { a <- fXort; b <- opt2xor } yield a + b
    
  • 将您的返回类型更改为XorT[Future, ServiceError, Option[X]](如果您需要在其余的理解中使用X,这可能没用):

    val optInside: FutureErrorOr[Option[Int]] = 
      XorT.fromXor[Future](oXorT.value.sequenceU)
    
    for { a <- fXorT; b <- optInside } yield b.map(_ + a)
    

答案 1 :(得分:1)

解决此问题的一种可能方法是针对不同类型(ContainerFuture)制作常见的Option monad:

trait Container[+A] {
  def map[B](f: A => B): Container[B]
  def flatMap[B](f: A => Container[B]): Container[B]
}
// Empty container for value
class EmptyContainer[+A](value: A) extends Container[A] {
  override def map[B](f: (A) => B): Container[B] = new EmptyContainer[B](f(value))
  override def flatMap[B](f: (A) => Container[B]): Container[B] = f(value)
}
// Implement container for Option
class OptionContainer[+A](option: Option[A]) extends Container[A] {
  override def map[B](f: (A) => B): Container[B] = new OptionContainer[B](option.map(f))
  override def flatMap[B](f: (A) => Container[B]): Container[B] = option match {
    case Some(value) => f(value)
    case None => new OptionContainer[B](None)
  }
}
// Implement container for Future
class FutureContainer[+A](future: Future[A]) extends Container[A] {
  override def map[B](f: (A) => B): Container[B] = new FutureContainer[B](future.map(f))
  // TODO: can be better!!!
  override def flatMap[B](f: (A) => Container[B]): Container[B] = {
    val promise = Promise[B]()
    future.onComplete {
      case Success(a) => f(a).map(b => promise.success(b))
      case Failure(exception) => promise.failure(exception)
    }
    new FutureContainer[B](promise.future)
  }
}

您可以为任何其他类型添加自己的实现。

// Monad for Container
object Container {
  implicit def monad = new Monad[Container] {
    def flatMap[A, B](fa: Container[A])(f: (A) => Container[B]): Container[B] = fa.flatMap(f)
    def pure[A](x: A): Container[A] = new EmptyContainer[A](x)
  }
}

我们的服务现在有了观点:

class SomeContainerService {
  def getSomeDate(): XorT[Container, Error, SomeData] =
    XorT.right(Option(SomeData()).toContainer)

  def getRemoteDate(): XorT[Container, Error, SomeData] =
    XorT.right(Future(SomeData()).toContainer)
}

未来和期权的扩展方法:

def toContainer = OptionContainer(option)
def toContainer = FutureContainer(future)

并且理解工作很好:

val result: XorT[Container, Error, SomeData] = for {
  data1 <- someContainerService.getRemoteDate() // future
  data2 <- someContainerService.getSomeDate()   // option
} yield {
  mergeResult(data1, data2)
}