Validation versus disjunction

后端 未结 2 622
深忆病人
深忆病人 2020-12-02 14:51

Suppose I want to write a method with the following signature:

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


        
2条回答
  •  佛祖请我去吃肉
    2020-12-02 15:35

    The following is a pretty close translation of the second version of my code for Cats:

    import scala.util.Try
    
    case class InvalidSizes(x: Int, y: Int) extends Exception(
      s"Error: $x is not smaller than $y!"
    )
    
    def parseInt(input: String): Either[Throwable, Int] = Try(input.toInt).toEither
    
    def checkValues(p: (Int, Int)): Either[InvalidSizes, (Int, Int)] =
      if (p._1 >= p._2) Left(InvalidSizes(p._1, p._2)) else Right(p)
    
    import cats.data.{EitherNel, ValidatedNel}
    import cats.instances.either._
    import cats.instances.list._
    import cats.syntax.apply._
    import cats.syntax.either._
    import cats.syntax.traverse._
    
    def checkParses(p: (String, String)): EitherNel[Throwable, (Int, Int)] =
      (parseInt(p._1).toValidatedNel, parseInt(p._2).toValidatedNel).tupled.toEither
    
    def parse(input: List[(String, String)]): ValidatedNel[Throwable, List[(Int, Int)]] =
      input.traverse(fields =>
        checkParses(fields).flatMap(s => checkValues(s).toEitherNel).toValidated
      )
    

    To update the question, this code is "bouncing back and forth between ValidatedNel and Either as appropriate depending on whether I need error accumulation or monadic binding".

    In the almost six years since I asked this question, Cats has introduced a Parallel type class (improved in Cats 2.0.0) that solves exactly the problem I was running into:

    import cats.data.EitherNel
    import cats.instances.either._
    import cats.instances.list._
    import cats.instances.parallel._
    import cats.syntax.either._
    import cats.syntax.parallel._
    
    def checkParses(p: (String, String)): EitherNel[Throwable, (Int, Int)] =
      (parseInt(p._1).toEitherNel, parseInt(p._2).toEitherNel).parTupled
    
    def parse(input: List[(String, String)]): EitherNel[Throwable, List[(Int, Int)]] =
      input.parTraverse(fields =>
        checkParses(fields).flatMap(checkValues(_).toEitherNel)
      )
    

    We can switch the the par version of our applicative operators like traverse or tupled when we want to accumulate errors, but otherwise we're working in Either, which gives us monadic binding, and we no longer have to refer to Validated at all.

提交回复
热议问题