2017-09-13 2 views
1

Je suis en train d'utiliser optionT, où mes fonctions reviennent Future[Option[T]]Comment utiliser OptionT lorsque l'un de mes expressions retourne un avenir [Seq [utilisateur]]

Un de mes appels renvoie une Future[Seq[T]], comment dois-je gérer ce cas?

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) 

Mise à jour J'ai aussi une méthode qui retourne un ensemble [Int]. Je ne sais pas comment envelopper dans une optionT, j'ai essayé:

.liftM[OptionT] mais cela n'a pas fonctionné.

+0

Ce que je fais habituellement est de tourner les options dans les listes avec 'toList' pas sûr qu'il ya un' toSeq'. – pedrofurla

Répondre

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

qu'en est-il d'une méthode qui retourne un Set [Int]. Dois-je l'emballer avec un avenir et liftM [OptionT]? J'ai essayé mais je n'ai pas travaillé. – Blankman

+0

Pas besoin de le soulever dans 'OptionT' dans ce cas, vous pouvez écrire' result = 'dans la for-comprehension. –

1

Voici un exemple complet pour ce que vous demandez, y compris la mise à jour à la question (une méthode qui retourne Seq[Employee], en dehors du Future contexte):

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)))) 

A noter cependant que, comme @ Zhang Liu mentionné dans les commentaires à sa réponse, vous n'avez pas à l'ajouter au contexte OptionT. Vous appelez probablement cette méthode depuis le bloc yield.

Mise à jour
Un peu sans rapport avec votre question, mais utile de se rappeler que cela aide les scénarios de test comme le vôtre, vous pouvez aussi abstrait le Future loin, il est donc plus facile de tester votre code sans se soucier de ExecutionContexts etc.

Mon code précédent devient alors:

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))))