Подтвердить что ты не робот

Валидация против дизъюнкции

Предположим, что я хочу написать метод со следующей сигнатурой:

def parse(input: List[(String, String)]):
  ValidationNel[Throwable, List[(Int, Int)]]

Для каждой пары строк на входе необходимо проверить, что оба элемента могут анализироваться как целые числа, а первое меньше второго. Затем он должен возвращать целые числа, накапливая любые возникающие ошибки.

Сначала я определяю тип ошибки:

import scalaz._, Scalaz._

case class InvalidSizes(x: Int, y: Int) extends Exception(
  s"Error: $x is not smaller than $y!"
)

Теперь я могу реализовать свой метод следующим образом:

def checkParses(p: (String, String)):
  ValidationNel[NumberFormatException, (Int, Int)] =
  p.bitraverse[
    ({ type L[x] = ValidationNel[NumberFormatException, x] })#L, Int, Int
  ](
    _.parseInt.toValidationNel,
    _.parseInt.toValidationNel
  )

def checkValues(p: (Int, Int)): Validation[InvalidSizes, (Int, Int)] =
  if (p._1 >= p._2) InvalidSizes(p._1, p._2).failure else p.success

def parse(input: List[(String, String)]):
  ValidationNel[Throwable, List[(Int, Int)]] = input.traverseU(p =>
    checkParses(p).fold(_.failure, checkValues _ andThen (_.toValidationNel))
  )

Или, альтернативно:

def checkParses(p: (String, String)):
  NonEmptyList[NumberFormatException] \/ (Int, Int) =
  p.bitraverse[
    ({ type L[x] = ValidationNel[NumberFormatException, x] })#L, Int, Int
  ](
    _.parseInt.toValidationNel,
    _.parseInt.toValidationNel
  ).disjunction

def checkValues(p: (Int, Int)): InvalidSizes \/ (Int, Int) =
  (p._1 >= p._2) either InvalidSizes(p._1, p._2) or p

def parse(input: List[(String, String)]):
  ValidationNel[Throwable, List[(Int, Int)]] = input.traverseU(p =>
    checkParses(p).flatMap(s => checkValues(s).leftMap(_.wrapNel)).validation
  )

Теперь по какой-либо причине первая операция (проверка того, что пары анализируются как строки) воспринимает меня как проблему проверки, в то время как вторая (проверка значений) воспринимается как проблема дизъюнкции, и мне кажется, что мне нужно составить два монадически (что предполагает, что я должен использовать \/, так как ValidationNel[Throwable, _] не имеет экземпляра монады).

В моей первой реализации я использую ValidationNel всюду, а затем fold в конце как своего рода подделка flatMap. Во втором случае я подпрыгиваю назад и вперед между ValidationNel и \/ в зависимости от того, нужно ли мне скопировать ошибки или монадическое связывание. Они дают одинаковые результаты.

Я использовал оба подхода в реальном коде и еще не разработал предпочтение друг другу. Я что-то упускаю? Должен ли я отдать предпочтение другому?

4b9b3361

Ответ 1

Это, вероятно, не тот ответ, который вы ищете, но я заметил, что Validation имеет следующие методы

/** Run a disjunction function and back to validation again. Alias for `@\/` */
def disjunctioned[EE, AA](k: (E \/ A) => (EE \/ AA)): Validation[EE, AA] =
  k(disjunction).validation

/** Run a disjunction function and back to validation again. Alias for `disjunctioned` */
def @\/[EE, AA](k: (E \/ A) => (EE \/ AA)): Validation[EE, AA] =
  disjunctioned(k)

Когда я их увидел, я не мог понять их полезность, пока не вспомнил этот вопрос. Они позволяют выполнять правильное связывание, преобразовывая его в дизъюнкцию.

def checkParses(p: (String, String)):
  ValidationNel[NumberFormatException, (Int, Int)] =
  p.bitraverse[
    ({ type L[x] = ValidationNel[NumberFormatException, x] })#L, Int, Int
  ](
    _.parseInt.toValidationNel,
    _.parseInt.toValidationNel
  )

def checkValues(p: (Int, Int)): InvalidSizes \/ (Int, Int) =
  (p._1 >= p._2) either InvalidSizes(p._1, p._2) or p

def parse(input: List[(String, String)]):
  ValidationNel[Throwable, List[(Int, Int)]] = input.traverseU(p =>
    checkParses(p)[email protected]\/(_.flatMap(checkValues(_).leftMap(_.wrapNel)))
  )