Вот простое, эффективное и функциональное решение проблемы.
Преимущество состоит в том, что Итераторы ленивые и 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 из Option лучшим типом.
Например, вы можете просто удалить сопоставить в конце и вернуть Option из Попробуйте вместо этого, или как насчет использования пользовательского исключения для значения, которое не найдено?
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)