2017-02-10 21 views
14

我所有的API方法都返回Future [Option [T]],試圖找出如何優雅地執行下列操作:我的API全部返回Future [Option [T]],如何在for-compr中很好地結合它們

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

下面的代碼目前不編譯,因爲用戶,位置和地址都是選項[用戶],選擇[位置]和選項[地址]

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

我記得scalaz有OptionT,但我從來沒有真正使用它之前,不知道如何將它應用於我的情況。

如果說用戶,位置或地址實際上返回None,那麼在這種情況下需要將它應用於3個模型時,使用OptionT會發生什麼?

+0

因爲這是明確的'OptionT',也許它應該有一個'scalaz'標籤(也可能是'monad-transformers'和/或'scala-cats')? –

回答

21

一個完整的工作示例着想一些簡單的定義:

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

併爲完整起見,這裏的Scalaz自由的實現將是什麼樣子:

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

即我們必須嵌套理解,就像我們必須嵌套map來轉換例如Int的值可能在Future[Option[Int]]之內。

斯卡拉茲或貓的OptionT monad變壓器旨在允許您使用Future[Option[A]]類型的工作,而無需嵌套。例如,你可以這樣寫:

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) 

或者,如果你想要一個Future[Option[UserProfile]]你可以撥打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 

然後:

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

如果任何中間結果是None,整件事情將是None

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

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

當然,如果有任何請求失敗,整個事情都會失敗並出現第一個錯誤。

作爲一個註腳,如果要求不依賴於對方,你可以撰寫他們合用地,而不是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 

該模型的計算,並且可以更有效,因爲它可以運行並行請求。

+0

這就是我所說的一個本壘打的男人,謝謝! | @ |就像Future.sequence? – Blankman

+0

@Blankman是的,最大的不同在於它可以與不同類型(維護類型和元素)一起工作。 –

+1

供參考:'Future.onSuccess'自Scala 2.12 已棄用https://github.com/viktorklang/blog/blob/master/Futures-in-Scala-2.12-part-5。md – n4to4

相關問題