在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))
}
}
}
}
}
答案 0 :(得分:2)
只有最后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)
}
}
(未经测试,但应该大致正确)
答案 1 :(得分:2)
您可能需要考虑将中间结果包装在Throwables中。然后,您以后可以恢复自己的未来-仅对那些异常进行模式匹配。
我添加了“样板”以使示例可编译:
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)
}
}
答案 2 :(得分:1)
考虑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
这是工作中的example
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)
请注意,我们如何true
/ false
代替Right
/ Left
来进行验证。