在出错时用失败来破坏集合的"查找"方法的惯用 Scala 方法是什么?


def findUseById(userId: Long, usersJson: Seq[JsObject]): Try[Option[JsObject]] = {
usersJson.find { userJson =>
(userJson  "id").validate[Long] {
case Right(uid) => uid == userId
case Left(error) =>
// How to break find loop and  "map" it to failure 
// (like Failure(new SomeException())) ?
}
}
}

打破查找循环并将其映射到失败的惯用方法是什么?

  • 我想避免使用异常(抛出异常并将其包装到 Try((( 中(
  • 我想避免使用return
  • 这应该或多或少是高性能的(所以递归是行不通的,我相信也不会折叠,因为它会扫描集合到最后(。

这是一个简单、高性能和功能性的解决方案。
它利用了迭代器collectFirst提前返回的优势。

import scala.util.{Failure, Success, Try}
def validateAndFind[A, B](data: Seq[A], target: B)
(validationFun: A => Either[Throwable, (A, B)]): Try[Option[A]] =
data
.iterator
.map(validationFun)
.collectFirst {
case Right((elem, value)) if (value == target) => Right(elem)
case Left(ex)                                  => Left(ex)
} match {
case None              => Success(None)
case Some(Right(elem)) => Success(Some(elem))
case Some(Left(ex))    => Failure(ex)
}

但是,我不确定尝试期权是否是最佳类型。例如,您可以只删除最后的匹配项并返回Try的选项,或者只对找不到值使用自定义异常怎么样?

sealed trait ValidationError extends Product with Serializable
final case object ElementNotFound extends ValidationError
final case class ValidationFailure(cause: Throwable) extends ValidationError
def validateAndFind[A, B](data: Seq[A], target: B)
(validationFun: A => Either[Throwable, (A, B)]): Either[ValidationError, A] =
data
.iterator
.map(validationFun)
.collectFirst {
case Right((elem, value)) if (value == target) => Right(elem)
case Left(ex)                                  => Left(ValidationFailure(cause = ex))
}.getOrElse(Left(ElementNotFound))

无论如何,这两个代码是等效的,并且按预期工作:

final case class User(name: String, age: Int)
validateAndFind(
data = List(
User(name = "Balmung", age = 22),
User(name = "Luis", age = 22),
User(name = "Miguel", age = 22)
),
target = "Luis"
) { user =>
println(s"Validating user: ${user}")
if (user.age < 18) Left(new IllegalArgumentException("User underage"))
else Right(user -> user.name)
}
// Validating user: User(Balmung,22)
// Validating user: User(Luis,22)
// res: Either[ValidationError, User] = Right(User("Luis", 22))

如您所见,它没有验证第三个元素,因为它不是必需的。
让我们看看其他两个要求。

validateAndFind(
data = List(
User(name = "Balmung", age = 16),
User(name = "Luis", age = 22),
User(name = "Miguel", age = 22)
),
target = "Luis"
) { user =>
println(s"Validating user: ${user}")
if (user.age < 18) Left(new IllegalArgumentException("User underage"))
else Right(user -> user.name)
}
// Validating user: User(Balmung,16)
// res: Either[ValidationError, User] = Left(ValidationFailure(java.lang.IllegalArgumentException: User underage))

validateAndFind(
data = List(
User(name = "Balmung", age = 22),
User(name = "Luis", age = 22),
User(name = "Miguel", age = 22)
),
target = "Mario"
) { user =>
println(s"Validating user: ${user}")
if (user.age < 18) Left(new IllegalArgumentException("User underage"))
else Right(user -> user.name)
}
// Validating user: User(Balmung,22)
// Validating user: User(Luis,22)
// Validating user: User(Miguel,22)
// res: Either[ValidationError, User] = Left(ElementNotFound)

也许

def findUseById(userId: Long, usersJson: Seq[JsObject]): Try[Option[JsObject]] =
Try(usersJson.find(userJson => (userJson  "id").validateOpt[Long].map(_ == userId).get)    

尽管这违反了您的第一个要求。

最新更新