Scala:逐行检查无,或者检查最后的异常?

时间:2015-10-05 08:16:42

标签: scala exception

我有一个案例类Private Sub Util_CellFormatting(ByVal Sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvUtil.CellFormatting If dgvUtil.CurrentCell IsNot Nothing Then If e.RowIndex = dgvUtil.CurrentCell.RowIndex And e.ColumnIndex = dgvUtil.CurrentCell.ColumnIndex And (dgvUtil.CurrentCell.ColumnIndex = 10 Or dgvUtil.CurrentCell.ColumnIndex = 11 Or dgvUtil.CurrentCell.ColumnIndex = 13) Then e.CellStyle.SelectionBackColor = Color.SteelBlue Else e.CellStyle.SelectionBackColor = dgvUtil.DefaultCellStyle.SelectionBackColor End If End If End Sub ,它有一些输入端口,每个端口都有一个名称。然后我有另一个案例类,它将值分配给应用程序的端口。

Application

我在数据库中有应用程序及其端口信息,我得到case class Port (id: ObjectId, name: String, PortType: String) case class Application (id: ObjectId, ports: List[Port]) case class AppRun (appId: ObjectId, assignments: List[Assignment]) case class Assignment (portName: String, value: String, valueType: String) 作为输入。我需要列出AppRun类型(下面)列表,显示分配给每个端口的值(并且匹配是在端口名称上完成的):

PortValue

在匹配过程中可能会出现一些问题:应用程序ID无效,端口不匹配等等。编写简单的算法然后捕获所有异常对我来说很自然,但这似乎是Java-ish。另一方面,我无法想到一种处理case class PortValue (portId: ObjectId, value: String) 的简洁方法,逐一检查它们,这会使代码混淆。

问题是你如何解决这种Scala方式?

编辑:当发生这种不匹配时,我需要发回适当的消息,例如"找不到应用程序"等等。

1 个答案:

答案 0 :(得分:3)

逐个处理Option检查的方法是使用for-understanding。如果你想跟踪错误,你可以经常用一些做错误跟踪的类替换Option。常见的可能性包括:

  • scala.util.Try[T]Try可以是Success(result),也可以是Failure(error: Throwable)。它是Scala中的内置类,如果需要,可以很容易地将其与scala.concurrent.Future组合或替换它。
  • scala.util.Either[E, T]。为每个错误创建Throwable可能效率不高,因为需要构建堆栈跟踪。因此,如果错误可以是简单的Either或某些特定于应用程序的类而没有堆栈跟踪,则String非常有用。惯例是Right(result)Left(error)。缺点是,没有语义才能拥有正确的'意味着成功'并且'离开'意味着错误',当你在for-comprehension或call中使用它时,例如关于它的map方法,您必须指定是否需要either.righteither.left
  • scalaz.\/[E, T]这与Either相同,但map和for-comprehension的默认值是其右侧(\/-)。此外,scalaz提供了非常有用的功能sequencetraverse(请参阅下面的代码)。
  • scalaz.Validation[Errors, T]scalaz.ValidationNel[E, T]。添加了一个非常有用的收集所有错误的功能,但在用于理解时会有轻微的问题。

以下是您的问题的示例代码,使用Try

import scala.util.{Try, Success, Failure}

def getApplication(appId: ObjectId): Option[Application] = ???

/** Convert Option to Try, using a given failure in case of None */
def toTry[T](option: Option[T])(failure: => Throwable): Try[T] =
  option.fold[Try[T]](Failure(failure))(Success(_))

/** Convert a List of Try to a Try of List.
  * If all tries in the List are Success, the result is Success.
  * Otherwise the result is the first Failure from the list */
def sequence[T](tries: List[Try[T]]): Try[List[T]] =
  tries.find(_.isFailure) match {
    case Some(Failure(error)) => Failure(error)
    case _ => Success(tries.map(_.get))
  }
def traverse[T, R](list: List[T])(f: T => Try[R]): Try[List[R]] =
  sequence(list map f)

def portValues(task: AppRun): Try[List[PortValue]] = for {
  app <- toTry(getApplication(task.appId))(
    new RuntimeException("application not found"))
  portByName = app.ports.map(p => p.name -> p).toMap
  ports <- traverse(task.assignments) { assignment =>
    val tryPort = toTry(portByName.get(assignment.portName))(
      new RuntimeException(s"no port named ${assignment.portName}"))
    tryPort.map(port => PortValue(port.id, assignment.value))
  }
} yield ports

一些注意事项:

  • 如果toTrysequencetraverse的实施只是一个示例。首先,我在implicit class es中定义它们,以便能够像普通方法一样调用它们(例如option.toTry(error)list.traverse(f))。
  • traverse可以更有效地实施(在找到第一个错误后停止)。
  • sequence实现仅返回第一个错误端口。
  • 我更喜欢def getApplication(id: ObjectId): Try[Application]之类的API而不是Option结果,因为您通常希望在调用它的代码的每个部分都有相同的错误,并且它也可能会产生不同的错误(例如,找不到ID或网络错误)。如果您有def getApplication(id: ObjectId): Application可能会引发错误,则只需将其打包在Try中:for { app <- Try(getApplication(id)) ...