当前位置: 首页 > 知识库问答 >
问题:

如何将以下平面图/地图片段转换为Scala中的理解?

令狐嘉运
2023-03-14

对于理解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))
        }
      }
    }
  }
}

共有3个答案

鱼志学
2023-03-14

考虑< code >或者重构

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/false代替了true/仍旧

耿俊
2023-03-14

您可能想考虑将中间结果包装在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)
  }
}
吴城
2023-03-14

只有最后一个其他之后的部分才真正适合理解:

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)
    }
  }

(未测试,但应大致正确)

 类似资料: