在 Scala 中,什么是理解以下代码片段的最佳形式(无衬里/回调,更少的样板(?
val result = emailTakenFuture.flatMap { emailTaken =>
if (emailTaken) {
Future.successful(SignUpResult.EmailAlreadyTaken)
} else {
usernameTakenFuture.flatMap { usernameTaken =>
if (usernameTaken) {
Future.successful(SignUpResult.UsernameAlreadyTaken)
} else {
nextIdFuture.flatMap { userId =>
storeUserFuture(userId).map(user => SignUpResult.Success(user))
}
}
}
}
}
只有最后一个else
之后的部分才真正适合理解:
for {
userId <- nextIdFuture
user <- storeUserFuture(userId)
} yield SignUpResult.Success(user)
我只想为其余部分编写一个辅助函数:
def condFlatMap[T](future: Future[Boolean], ifTrue: T)(ifFalse: => Future[T]): Future[T] =
future.flatMap(x => if (x) Future.successful(ifTrue) else ifFalse)
val result =
condFlatMap(emailTakenFuture, SignUpResult.EmailAlreadyTaken) {
condFlatMap(usernameTakenFuture, SignUpResult.UsernameAlreadyTaken) {
for {
userId <- nextIdFuture
user <- storeUserFuture(userId)
} yield SignUpResult.Success(user)
}
}
(未测试,但应该大致正确(
您可能需要考虑将中介结果包装在可抛掷对象中。然后,您可以稍后恢复您的未来 - 仅针对这些异常的模式匹配。
我包含"样板"以使示例可编译:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
implicit val executionContext: ExecutionContext = ExecutionContext.global
case class User()
def emailTakenFuture: Future[Boolean] = ???
def usernameTakenFuture: Future[Boolean] = ???
def nextIdFuture: Future[String] = ???
def storeUserFuture(userId: String): Future[User]
为了简洁起见,我扩展了 Throwable。您可能希望将注册结果包装在自定义异常中,以免将它们与SignupResult
类型一起公开。
trait SignUpResult
case object SignUpResult {
case object EmailAlreadyTaken extends Throwable with SignUpResult
case object UsernameAlreadyTaken extends Throwable with SignUpResult
case class Success(user: User) extends SignUpResult
}
val result: Future[SignUpResult] = {
(for {
emailTaken <- emailTakenFuture
_ <- if (emailTaken) Future.failed(SignUpResult.EmailAlreadyTaken) else Future.successful(Unit)
userNameTaken <- usernameTakenFuture
_ <- if (userNameTaken) Future.failed(SignUpResult.UsernameAlreadyTaken) else Future.successful(Unit)
userId <- nextIdFuture
user <- storeUserFuture(userId)
} yield SignUpResult.Success(user)).recoverWith {
case (SignUpResult.EmailAlreadyTaken) => Future.successful(SignUpResult.EmailAlreadyTaken)
case (SignUpResult.UsernameAlreadyTaken) => Future.successful(SignUpResult.UsernameAlreadyTaken)
}
}
考虑EitherT
type SignupResult[A] = EitherT[Future, SignupError, A]
其中SignupError
是以下 ADT:
sealed trait SignupError
case object EmailAlreadyTaken extends SignupError
case object UsernameAlreadyTaken extends SignupError
case object UserIdError extends SignupError
case object UserCreationError extends SignupError
然后给定以下方法签名
def validateEmail(email: String): SignupResult[Unit] = ???
def validateUsername(username: String): SignupResult[Unit] = ???
def nextId(): SignupResult[String] = ???
def storeUser(userId: String): SignupResult[User] = ???
流向平坦到一个干净的
理解(for {
_ <- validateEmail("picard@starfleet.org")
_ <- validateUsername("picard")
userId <- nextId()
user <- storeUser(userId)
} yield user).value
这是一个工作示例
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object EitherTExample extends App {
sealed trait SignupError
case object EmailAlreadyTaken extends SignupError
case object UsernameAlreadyTaken extends SignupError
case object UserIdError extends SignupError
case object UserCreationError extends SignupError
final case class User(id: String, username: String)
type SignupResult[A] = EitherT[Future, SignupError, A]
def validateEmail(email: String): SignupResult[Unit] = EitherT.rightT(())
def validateUsername(username: String): SignupResult[Unit] = EitherT.leftT(UsernameAlreadyTaken)
def nextId(): SignupResult[String] = EitherT.rightT("42424242")
def storeUser(userId: String): SignupResult[User] = EitherT.rightT(User("42424242", "picard"))
val result: Future[Either[SignupError, User]] =
(for {
_ <- validateEmail("picard@starfleet.org")
_ <- validateUsername("picard")
userId <- nextId()
user <- storeUser(userId)
} yield user).value
result.map(v => println(v))
}
哪些输出
Left(UsernameAlreadyTaken)
请注意,我们如何Right
/Left
而不是用于验证目的的true
/false
。