Как идиоматический c Scala способ сломать метод `find` коллекции с помощью Failure в случае ошибки? - PullRequest
4 голосов
/ 22 января 2020
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())) ?
    }
  }
}

Каким будет идиоматический c способ взлома находки l oop и сопоставления его с ошибкой?

  • Я хочу избежать использования исключений (выбрасывать исключение и переносить его в Try ())
  • Я хочу избегать использования return
  • Это должно быть более или менее производительным (поэтому я считаю, что рекурсия не будет работать, ни свернуть, потому что она сканирует коллекцию в конец).

Ответы [ 2 ]

4 голосов
/ 22 января 2020

Вот простое, эффективное и функциональное решение проблемы.
Преимущество состоит в том, что Итераторы ленивые и 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)
2 голосов
/ 22 января 2020

Возможно

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

, хотя это нарушает ваше первое требование.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...