当我的一个表达式返回Future [Seq [User]]时如何使用OptionT

时间:2017-09-13 01:21:05

标签: scala scalaz

我正在尝试使用optionT,我的函数返回Future[Option[T]]

我的一个电话会回复Future[Seq[T]],我应该如何处理这个案例?

for {
  user <- OptionT(api.getUser(123))
  company <- OptionT(api.getCompany(user.companyId))
  employees <- api.getEmployees(company.id) // returns Future[Seq[Employee]]
} yield CompanyProfile(user, company, employees)

更新的 我还有一个返回Set [Int]的方法。不知道怎么把它包装成一个OptionT,我试过了:

.liftM[OptionT]但是那不起作用。

2 个答案:

答案 0 :(得分:1)

employees <- OptionT(api.getEmployees(company.id).map(Option(_)))

答案 1 :(得分:1)

以下是您要问的完整示例,包括问题的更新(在Seq[Employee]上下文之外返回Future的方法):

import scala.concurrent.ExecutionContext.Implicits.global
import cats.data.OptionT
import cats.implicits._

case class Employee(x : String)
case class User(x : String)
case class Company(x : String)
case class CompanyProfile(user : User, company: Company, employees: Seq[Employee])

def emptyEmployees : Future[Seq[Employee]] = Future.successful(Seq())
def nonEmptyEmployees : Future[Seq[Employee]] = Future.successful(Seq(Employee("Test1"),Employee("Test2")))
def user : Future[Option[User]] = Future.successful(Some(User("user1")))
def company : Future[Option[Company]] = Future.successful(Some(Company("company1")))

val res: OptionT[Future, CompanyProfile] = for{
  user <- OptionT(user)
  company <- OptionT(company)
  employeesOne <-  OptionT(nonEmptyEmployees.map(Option(_))) // Here you wrap the `Seq[Employee]` into an `Option`
  employeesTwo <- OptionT.pure[Future, Seq[Employee]](employees) // Here you lift the `Seq[Employee]` into the context of `OptionT[Future, Seq[Employee]]`
} yield CompanyProfile(user, company, employeesOne ++ employeesTwo)

Await.result(res.value, Duration.Inf) 
   // Option[CompanyProfile] = Some(CompanyProfile(User(user1),Company(company1),List(Employee(Test1), Employee(Test2), Employee(Test3), Employee(Test4))))

但请注意,正如@Zhang Liu在评论中提到的那样,你没有必须将其添加到OptionT上下文中。您可能会在yield块中调用该方法。

<强>更新
与您的问题有点无关,但有助于记住,因为它有助于测试像您这样的场景,您也可以抽象Future,所以更容易测试代码而不用担心ExecutionContexts等。< / p>

我之前的代码将成为:

def emptyEmployeesM[M[_]](implicit m : Monad[M]) : M[Seq[Employee]] =
  m.pure(Seq.empty)

def nonEmptyEmployeesM[M[_]](implicit m : Monad[M]) : M[Seq[Employee]] =
  m.pure(Seq(Employee("Test1"),Employee("Test2")))

def employees : Seq[Employee] = Seq(Employee("Test3"),Employee("Test4"))

def userM[M[_]](implicit m : Monad[M]) : M[Option[User]] = m.pure(Some(User("user1")))
def companyM[M[_]](implicit m : Monad[M]) : M[Option[Company]] = m.pure(Some(Company("company1")))

def companyProfile[M[_]](implicit m : Monad[M]): OptionT[M, CompanyProfile] = for{
  user <- OptionT(userM[M])
  company <- OptionT(companyM[M])
  employeesOne <-  OptionT(Functor[M].map(nonEmptyEmployeesM[M])(Option(_)))
  employeesTwo <- OptionT.pure[M, Seq[Employee]](employees)
} yield CompanyProfile(user, company, employeesOne ++ employeesTwo)

// Here we still use Future
val res1 = Await.result(companyProfile[Future].value, Duration.Inf) // Option[CompanyProfile] = Some(CompanyProfile(User(user1),Company(company1),List(Employee(Test1), Employee(Test2), Employee(Test3), Employee(Test4))))

// However here we can use another Monad, in this case Id (which is simply a type alias to itself), which allows to test more easily your companyProfile method.
val res2 = companyProfile[Id].value // Option[CompanyProfile] = Some(CompanyProfile(User(user1),Company(company1),List(Employee(Test1), Employee(Test2), Employee(Test3), Employee(Test4))))