Предположим, что я хочу написать метод со следующей сигнатурой:
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
и \/
в зависимости от того, нужно ли мне скопировать ошибки или монадическое связывание. Они дают одинаковые результаты.
Я использовал оба подхода в реальном коде и еще не разработал предпочтение друг другу. Я что-то упускаю? Должен ли я отдать предпочтение другому?