我有一个案例类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方式?
编辑:当发生这种不匹配时,我需要发回适当的消息,例如"找不到应用程序"等等。
答案 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.right
或either.left
。scalaz.\/[E, T]
这与Either
相同,但map
和for-comprehension的默认值是其右侧(\/-
)。此外,scalaz
提供了非常有用的功能sequence
和traverse
(请参阅下面的代码)。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
一些注意事项:
toTry
,sequence
和traverse
的实施只是一个示例。首先,我在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)) ...