如何在 Scala 中将以下 flatMap/map 片段转换为理解?



在 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

最新更新