我的 API 全部返回 Future[Option[T]],如何在 for-compr 中很好地组合它们
My API is all returning Future[Option[T]], how to combine them nicely in a for-compr
我所有的 API 方法 return Future[Option[T]],试图找出如何优雅地执行以下操作:
case class UserProfile(user: User, location: Location, addresses: Address)
以下代码目前无法编译,因为用户、位置和地址都是 Option[User]、Option[Location] 和 Option[Address]
val up = for {
user <- userService.getById(userId)
location <- locationService.getById(locationId)
address <- addressService.getById(addressId)
} yield UserProfile(user, location, address)
我记得 scalaz 有 OptionT,但我以前从未真正使用过它,也不确定如何将它应用到我的情况中。
如果实际上说的是用户、位置或地址 return None,在这种情况下,当我需要将它应用到 3 个模型时,使用 OptionT 会发生什么情况?
为了完整的工作示例,一些简单的定义:
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-free 实现的样子:
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)
)
即我们必须嵌套 for-comprehensions,就像我们必须嵌套 map
才能进行转换一样可能在 Future[Option[Int]]
.
中的 Int
值
Scalaz 或 Cats 中的 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
当然,如果任何请求失败,整个过程都会失败并出现第一个错误。
作为脚注,如果请求不相互依赖,您可以应用组合而不是单子组合:
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
这可以更准确地模拟计算并且可能更高效,因为它可以 运行 并行请求。
我所有的 API 方法 return Future[Option[T]],试图找出如何优雅地执行以下操作:
case class UserProfile(user: User, location: Location, addresses: Address)
以下代码目前无法编译,因为用户、位置和地址都是 Option[User]、Option[Location] 和 Option[Address]
val up = for {
user <- userService.getById(userId)
location <- locationService.getById(locationId)
address <- addressService.getById(addressId)
} yield UserProfile(user, location, address)
我记得 scalaz 有 OptionT,但我以前从未真正使用过它,也不确定如何将它应用到我的情况中。
如果实际上说的是用户、位置或地址 return None,在这种情况下,当我需要将它应用到 3 个模型时,使用 OptionT 会发生什么情况?
为了完整的工作示例,一些简单的定义:
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-free 实现的样子:
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)
)
即我们必须嵌套 for-comprehensions,就像我们必须嵌套 map
才能进行转换一样可能在 Future[Option[Int]]
.
Int
值
Scalaz 或 Cats 中的 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
当然,如果任何请求失败,整个过程都会失败并出现第一个错误。
作为脚注,如果请求不相互依赖,您可以应用组合而不是单子组合:
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
这可以更准确地模拟计算并且可能更高效,因为它可以 运行 并行请求。