validation - logica - Validación versus disyunción
disyuncion y conjuncion (1)
Supongamos que quiero escribir un método con la siguiente firma:
def parse(input: List[(String, String)]):
ValidationNel[Throwable, List[(Int, Int)]]
Para cada par de cadenas en la entrada, es necesario verificar que ambos miembros puedan analizarse como enteros y que el primero sea más pequeño que el segundo. Luego debe devolver los números enteros, acumulando cualquier error que aparezca.
Primero definiré un tipo de error:
import scalaz._, Scalaz._
case class InvalidSizes(x: Int, y: Int) extends Exception(
s"Error: $x is not smaller than $y!"
)
Ahora puedo implementar mi método de la siguiente manera:
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))
)
O alternativamente:
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
)
Ahora, por la razón que sea, la primera operación (validando que los pares se analizan como cadenas) me parece un problema de validación, mientras que la segunda (verificando los valores) se siente como un problema de disyunción, y siento que necesito componer los dos de manera monádica ( lo que sugiere que debería estar usando //
, ya que ValidationNel[Throwable, _]
no tiene una instancia de mónada).
En mi primera implementación, uso ValidationNel
todas partes y luego me fold
al final como una especie de flatMap
falso. En el segundo, rebote entre ValidationNel
y //
según sea apropiado, dependiendo de si necesito la acumulación de errores o el enlace monádico. Producen los mismos resultados.
He usado ambos enfoques en código real, y aún no he desarrollado una preferencia por uno sobre el otro. ¿Me estoy perdiendo de algo? ¿ Prefiero uno sobre el otro?
Probablemente esta no sea la respuesta que está buscando, pero me di cuenta de que la Validation
tiene los siguientes métodos
/** 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)
Cuando los vi, realmente no podía ver su utilidad hasta que recordé esta pregunta. Le permiten hacer un enlace adecuado convirtiéndose a disyunción.
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).@//(_.flatMap(checkValues(_).leftMap(_.wrapNel)))
)