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

Scala如何处理理解中的条件?

蔡鹏程
2023-03-14

我试图创建一个简洁的结构,用于理解基于未来的业务逻辑。下面是一个示例,其中包含一个基于异常处理的工作示例:

(for {
  // find the user by id, findUser(id) returns Future[Option[User]]
  userOpt <- userDao.findUser(userId)        
  _ = if (!userOpt.isDefined) throw new EntityNotFoundException(classOf[User], userId)

  user = userOpt.get       

  // authenticate it, authenticate(user) returns Future[AuthResult]
  authResult <- userDao.authenticate(user)   
  _ = if (!authResult.ok) throw new AuthFailedException(userId)

  // find the good owned by the user, findGood(id) returns Future[Option[Good]]
  goodOpt <- goodDao.findGood(goodId)        
  _ = if (!good.isDefined) throw new EntityNotFoundException(classOf[Good], goodId)

  good = goodOpt.get        

  // check ownership for the user, checkOwnership(user, good) returns Future[Boolean]
  ownership <- goodDao.checkOwnership(user, good)
  if (!ownership) throw new OwnershipException(user, good)

  _ <- goodDao.remove(good) 
} yield {
  renderJson(Map(
    "success" -> true
  ))
})
.recover {
  case ex: EntityNotFoundException =>
    /// ... handle error cases ...
    renderJson(Map(
        "success" -> false,
        "error" -> "Your blahblahblah was not found in our database"
    ))
  case ex: AuthFailedException =>
    /// ... handle error cases ...
  case ex: OwnershipException =>
    /// ... handle error cases ...
}

然而,这可能被视为一种非功能性或非Scala的处理方式。有更好的方法吗?

请注意,这些错误来自不同的来源——有些在业务级别(“检查所有权”),有些在控制器级别(“授权”),有些在数据库级别(“找不到实体”)。因此,从单一常见错误类型派生它们的方法可能不起作用。

共有3个答案

经嘉
2023-03-14

核心挑战是,对于理解来说,一次只能处理一个单子,在这种情况下,它就是未来的单子,而缩短未来调用序列的唯一方法就是让未来失败。这是因为for CONTRUMENT中的后续调用只是调用map和flatmap,而失败的Future上的map的行为是返回该Future,而不是执行提供的主体(即被调用的函数)。

您试图实现的是基于某些条件的工作流的短期循环,而不是通过失败的未来来实现。这可以通过将结果包装到另一个容器中来实现,我们称之为结果[A],这给理解提供了一种未来[结果[A]]的类型<代码>结果将包含结果值,或是终止结果。挑战在于如何:

  • 提供后续函数调用先前非终止结果所包含的值
  • 如果结果正在终止,则阻止对后续函数调用求值

map/平面图似乎是进行这些类型组合的候选者,除了我们必须手动调用它们,因为for理解可以评估的唯一map/平面图是导致Future[Result[A]]

结果可以定义为:

trait Result[+A] {

  // the intermediate Result
  def value: A

  // convert this result into a final result based on another result
  def given[B](other: Result[B]): Result[A] = other match {
    case x: Terminator => x
    case v => this
  }

  // replace the value of this result with the provided one
  def apply[B](v: B): Result[B]

  // replace the current result with one based on function call
  def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]]

  // create a new result using the value of both
  def combine[B](other: Result[B]): Result[(A, B)] = other match {
    case x: Terminator => x
    case b => Successful((value, b.value))
  }
}

对于每个调用,该操作实际上是一个潜在的操作,因为调用它或使用终止结果,将只维护终止结果。请注意,终止符是一个结果[无],因为它从不包含值,任何结果[无]都可以是结果[无]。

终止结果定义为:

sealed trait Terminator extends Result[Nothing] {
  val value = throw new IllegalStateException()

  // The terminator will always short-circuit and return itself as
  // the success rather than execute the provided block, thus
  // propagating the terminating result
  def flatMap[A2 >: Nothing, B](f: A2 => Future[Result[B]]): Future[Result[B]] =
    Future.successful(this)

  // if we apply just a value to a Terminator the result is always the Terminator
  def apply[B](v: B): Result[B] = this

  // this apply is a convenience function for returning this terminator
  // or a successful value if the input has some value
  def apply[A](opt: Option[A]) = opt match {
    case None => this
    case Some(v) => Successful[A](v)
  }

  // this apply is a convenience function for returning this terminator or
  // a UnitResult
  def apply(bool: Boolean): Result[Unit] = if (bool) UnitResult else this
}

当我们已经满足终止条件时,终止结果可以使对需要值的函数的调用短路。

非终止结果定义为:

trait SuccessfulResult[+A] extends Result[A] {

  def apply[B](v: B): Result[B] = Successful(v)

  def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]] = f(value)
}

case class Successful[+A](value: A) extends SuccessfulResult[A]

case object UnitResult extends SuccessfulResult[Unit] {
  val value = {}
}

现在让我们定义好的但终止的条件:

case object UserNotFound extends Terminator

case object NotAuthenticated extends Terminator

case object GoodNotFound extends Terminator

case object NoOwnership extends Terminator

现在我们有了创建您正在寻找的工作流程的工具。每个理解都想要一个函数,该函数在右侧返回Future[Result[A]],在左侧生成Result[A]Result[A]上的平面地图使得调用(或短路)一个需要[A]作为输入的函数成为可能,然后我们可以将其结果映射到一个新的Result

def renderJson(data: Map[Any, Any]): JsResult = ???
def renderError(message: String): JsResult = ???

val resultFuture = for {

  // apply UserNotFound to the Option to conver it into Result[User] or UserNotFound
  userResult <- userDao.findUser(userId).map(UserNotFound(_))

  // apply NotAuthenticated to AuthResult.ok to create a UnitResult or NotAuthenticated
  authResult <- userResult.flatMap(user => userDao.authenticate(user).map(x => NotAuthenticated(x.ok)))

  goodResult <- authResult.flatMap(_ => goodDao.findGood(goodId).map(GoodNotFound(_)))

  // combine user and good, so we can feed it into checkOwnership
  comboResult = userResult.combine(goodResult)

  ownershipResult <- goodResult.flatMap { case (user, good) => goodDao.checkOwnership(user, good).map(NoOwnership(_))}

  // in order to call removeGood with a good value, we take the original
  // good result and potentially convert it to a Terminator based on
  // ownershipResult via .given
  _ <- goodResult.given(ownershipResult).flatMap(good => goodDao.removeGood(good).map(x => UnitResult))
} yield {

  // ownership was the last result we cared about, so we apply the output
  // to it to create a Future[Result[JsResult]] or some Terminator
  ownershipResult(renderJson(Map(
    "success" -> true
  )))
}

// now we can map Result into its value or some other value based on the Terminator
val jsFuture = resultFuture.map {
  case UserNotFound => renderError("User not found")
  case NotAuthenticated => renderError("User not authenticated")
  case GoodNotFound => renderError("Good not found")
  case NoOwnership => renderError("No ownership")
  case x => x.value
}

我知道这需要大量的设置,但至少结果类型可以用于任何具有终止条件的理解。

宗政楚
2023-03-14

您可以稍微整理一下,使其看起来像这样:

  for {
    user <- findUser(userId)
    authResult <- authUser(user)      
    good <- findGood(goodId)
    _ <- checkOwnership(user, good)    
    _ <- goodDao.remove(good) 
  } yield {
    renderJson(Map(
      "success" -> true
    ))
  }

采用以下方法:

def findUser(id:Long) = find(id, userDao.findUser)
def findGood(id:Long) = find(id, goodDao.findGood)

def find[T:ClassTag](id:Long, f:Long => Future[Option[T]]) = {
  f(id).flatMap{
    case None => Future.failed(new EntityNotFoundException(implicitly[ClassTag[T]].runtimeClass, id))
    case Some(entity) => Future.successful(entity)
  }    
}

def authUser(user:User) = {
  userDao.authenticate(user).flatMap{
    case result if result.ok => Future.failed(new AuthFailedException(userId))
    case result => Future.successful(result)
  }    
}

def checkOwnership(user:User, good:Good):Future[Boolean] = {
  val someCondition = true //real logic for ownership check goes here
  if (someCondition) Future.successful(true)
  else Future.failed(new OwnershipException(user, good))
}

这里的想法是使用平面图(flatMap)将返回的、包装在未来的选项(Options)等转换为失败的未来(Future),如果它们是无的(None)。对于comp来说,有很多方法可以解决这个问题,这是一种可能的方法。

田曜瑞
2023-03-14

不要对预期行为使用异常。

它在Java中不友好,在Scala中也确实不友好。请参阅此问题以获取有关为什么应避免对常规控制流使用异常的更多信息。Scala非常适合避免使用异常:您可以使用要么s。

诀窍是定义您可能遇到的一些故障,并将您的选项转换为包装这些故障的任一选项。

// Failures.scala
object Failures {
   sealed trait Failure

   // Four types of possible failures here
   case object UserNotFound extends Failure
   case object NotAuthenticated extends Failure
   case object GoodNotFound extends Failure
   case object NoOwnership extends Failure
   // Put other errors here...

   // Converts options into Eithers for you
   implicit class opt2either[A](opt: Option[A]) {
      def withFailure(f: Failure) = opt.fold(Left(f))(a => Right(a))
   }
}

使用这些帮助,您可以使您的理解可读和无异常:

import Failures._    

// Helper function to make ownership checking more readable in the for comprehension
def checkGood(user: User, good: Good) = {
    if(checkOwnership(user, good))
        Right(good)
    else
        Left(NoOwnership)
}

// First create the JSON
val resultFuture: Future[Either[Failure, JsResult]] = for {
    userRes <- userDao.findUser(userId)
    user    <- userRes.withFailure(UserNotFound).right
    authRes <- userDao.authenticate(user)
    auth    <- authRes.withFailure(NotAuthenticated).right
    goodRes <- goodDao.findGood(goodId)
    good    <- goodRes.withFailure(GoodNotFound).right
    checkedGood <- checkGood(user, good).right
} yield renderJson(Map("success" -> true)))

// Check result and handle any failures 
resultFuture.map { result =>
    result match {
        case Right(json) => json // serve json
        case Left(failure) => failure match {
            case UserNotFound => // Handle errors
            case NotAuthenticated =>
            case GoodNotFound =>
            case NoOwnership =>
            case _ =>
        }
    }
}
 类似资料:
  • 我想在我的play scala Web应用程序中进行错误处理。 我的应用程序与数据库对话以获取一些行,它遵循以下流程。 < li >首先调用数据库以获取一些数据 < li >使用第一次调用中的数据从数据库中提取其他数据 < li >使用从最近两次db调用中收到的数据形成响应。 下面是我的伪代码。 以上理解中的每一个方法都返回一个未来,这些方法的签名如下。 在以下情况下,我该如何进行错误/故障处理

  • 我一直在努力在MySQL中输入空白值。例如,代码: 不会工作。我总是收到以下错误: 但是,当我运行以下两个语句之一时: 或 这个查询工作得很好。column2的默认值已设置为“0”,仍然没有运气。 我面临的问题是所有表中的所有列。我的申请将有空白条目(无法更改)。这个功能以前工作得很好,当我将应用程序代码转移到另一个服务器时,我开始出现这个错误。 MySQL服务器版本:8.0.22-MySQL社区

  • 我在学习Scala playframework教程时遇到了一段令我迷惑不解的代码: 于是我决定调查一下,偶然发现了这个帖子。

  • 主要内容:抛出异常,捕获异常,实例,finally 语句,实例Scala 的异常处理和其它语言比如 Java 类似。 Scala 的方法可以通过抛出异常的方法的方式来终止相关代码的运行,不必通过返回值。 抛出异常 Scala 抛出异常的方法和 Java一样,使用 throw 方法,例如,抛出一个新的参数异常: 捕获异常 异常捕捉的机制与其他语言中一样,如果有异常发生,catch 字句是按次序捕捉的。因此,在 catch 字句中,越具体的异常越要靠前,越普遍的

  • Scala 的异常处理和其它语言比如 Java 类似。 Scala 的方法可以通过抛出异常的方法的方式来终止相关代码的运行,不必通过返回值。 抛出异常 Scala 抛出异常的方法和 Java一样,使用 throw 方法,例如,抛出一个新的参数异常: throw new IllegalArgumentException 捕获异常 异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序

  • 问题内容: 在我使用任何一种编程语言的1个月的经验中,我假设 条件可以接受括号中的任何内容作为布尔检查事,例如: 明白我的意思了吗? 就像是 可悲的是,似乎没有这种方式。我不能在开关盒中进行布尔检查。 有办法解决吗? 顺便说一句,如果我听起来很困惑,那就非常抱歉。我还不太了解这种语言的名称:X 任何答复 问题答案: 您可以针对以下情况获得OR: 案例就像一个“ goto”,多个goto可以共享同一