一個完整的工作示例着想一些簡單的定義:
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
該模型的計算,並且可以更有效,因爲它可以運行並行請求。
因爲這是明確的'OptionT',也許它應該有一個'scalaz'標籤(也可能是'monad-transformers'和/或'scala-cats')? –