2017-02-10 9 views
14

Wszystkie moje metody API zwracają Future [Option [T]], próbując dowiedzieć się, jak elegancko wykonać następujące czynności :Moje API powraca w przyszłości [Opcja [T]], jak je ładnie połączyć w for-compr

case class UserProfile(user: User, location: Location, addresses: Address) 

poniższy kod aktualnie nie kompiluje ponieważ użytkownik, położenie i adres są Opcja [User], opcja [LOKALIZACJA] i opcja [adres]

val up = for { 
user <- userService.getById(userId) 
location <- locationService.getById(locationId) 
address <- addressService.getById(addressId) 
} yield UserProfile(user, location, address) 

pamiętam ten scalaz ma OptionT, ale nigdy tak naprawdę go nie używałem i nie wiem, jak zastosować go w mojej sytuacji.

Jeśli użytkownik, lokalizacja lub adres rzeczywiście zwraca Brak, co by się stało, gdy za pomocą OptionT trzeba zastosować go do 3 modeli w tym przypadku?

+0

Ponieważ jest to wyraźnie o 'OptionT', może powinien on mieć tag' scalaz' (i ewentualnie także 'monadransformatory' i/lub' scala-cats')? –

Odpowiedz

21

Kilka prostych definicji ze względu na kompletny przykład robocza:

import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.Future 

type User = String 
type Location = String 
type Address = String 

case class UserProfile(user: User, location: Location, addresses: Address) 

def getUserById(id: Long): Future[Option[User]] = id match { 
    case 1 => Future.successful(Some("Foo McBar")) 
    case _ => Future.successful(None) 
} 

def getLocationById(id: Long): Future[Option[Location]] = id match { 
    case 1 => Future.successful(Some("The Moon")) 
    case _ => Future.successful(None) 
} 

def getAddressById(id: Long): Future[Option[Address]] = id match { 
    case 1 => Future.successful(Some("123 Moon St.")) 
    case _ => Future.successful(None) 
} 

I przez wzgląd na kompletność, oto co Scalaz wolna implementacja będzie wyglądać następująco:

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = 
    for { 
    maybeUser  <- getUserById(uid) 
    maybeLocation <- getLocationById(lid) 
    maybeAddress <- getAddressById(aid) 
    } yield (
    for { 
     user  <- maybeUser 
     location <- maybeLocation 
     address <- maybeAddress 
    } yield UserProfile(user, location, address) 
) 

Tj musimy zagnieździć się w celu zrozumienia, tak jak byśmy musieli zagnieździć się, aby przekształcić np. map wartość Int, która może znajdować się wewnątrz Future[Option[Int]].

Transformator monadowy OptionT w Scalaz lub Cats został zaprojektowany, aby umożliwić pracę z typami takimi jak Future[Option[A]] bez tego zagnieżdżania. Na przykład można napisać to:

import scalaz.OptionT, scalaz.std.scalaFuture._ 

def getProfile(uid: Long, lid: Long, aid: Long): OptionT[Future, UserProfile] = 
    for { 
    user  <- OptionT(getUserById(uid)) 
    location <- OptionT(getLocationById(lid)) 
    address <- OptionT(getAddressById(aid)) 
    } yield UserProfile(user, location, address) 

Albo jeśli chciałeś Future[Option[UserProfile]] można po prostu zadzwonić run:

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = (
    for { 
    user  <- OptionT(getUserById(uid)) 
    location <- OptionT(getLocationById(lid)) 
    address <- OptionT(getAddressById(aid)) 
    } yield UserProfile(user, location, address) 
).run 

, a następnie:

scala> getProfile(1L, 1L, 1L).foreach(println) 
Some(UserProfile(Foo McBar,The Moon,123 Moon St.)) 

Jeżeli którykolwiek z wyników pośrednich są None, całość będzie None:

scala> getProfile(1L, 1L, 0L).foreach(println) 
None 

scala> getProfile(0L, 0L, 0L).foreach(println) 
None 

I oczywiście, jeśli któreś z żądań się nie powiedzie, wszystko kończy się niepowodzeniem z pierwszym błędem.

jako przypis, jeśli wnioski nie zależą od siebie, można komponować je applicatively zamiast monadically:

import scalaz.Scalaz._ 

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = (
    OptionT(getUserById(uid)) |@| 
    OptionT(getLocationById(lid)) |@| 
    OptionT(getAddressById(aid)) 
)(UserProfile.apply _).run 

This modelach obliczeniowych dokładniej i może być bardziej efektywne, ponieważ można go uruchomić wnioski równolegle.

+0

To jest to, co nazywam człowiekiem w domu, dzięki! | @ | jest jak Future.sequence? – Blankman

+0

@Blankman Yep-duża różnica polega na tym, że działa z różnymi typami (zachowując oba typy i arytmy). –

+1

FYI: 'Future.onSuccess' jest przestarzałe od Scala 2.12 https://github.com/viktorklang/blog/blob/master/Futures-in-Scala-2.12-part-5.md – n4to4

Powiązane problemy