Introducing Validated

The cats.data.Validated[E, A]type is very similar to Either[E, A]. It is an ADT that represents a value of either an Invalid type or a Valid type. A simplified definition would be the following:

sealed trait Validated[+E, +A]
case class Valid[+A](a: A) extends Validated[Nothing, A]
case class Invalid[+E](e: E) extends Validated[E, Nothing]

We will see what the + sign in front of a type parameter means in the section on covariance and contravariance in Chapter 4Advanced Features. For now, though, do not worry about it.

Similarly to the definition of Option, the definitions use contravariance and Nothing. This way, Valid[A] is a subtype of Validated[E, A] for any E; and Invalid[E] is a subtype of Validated[E, A] for any A.

The main difference with Either is that we can accumulate the errors produced by several Validated instances. Here are some examples that you can retype in a new Scala worksheet. I advise you to uncheck the Type-aware highlighting box in the bottom-right corner of IntelliJ; otherwise, IntelliJ will underline some expressions in red, even though they compile fine:

import cats.data._
import cats.data.Validated._
import cats.implicits._

val valid1: Validated[NonEmptyList[String], Int] = Valid(1)
// valid1: cats.data.Validated[cats.data.NonEmptyList[String],Int] = Valid(1)

val valid2 = 2.validNel[String]
// valid2: cats.data.ValidatedNel[String,Int] = Valid(2)

(valid1, valid2).mapN { case (i1, i2) => i1 + i2 }
// res1: cats.data.ValidatedNel[String,Int] = Valid(3)

val invalid3: ValidatedNel[String, Int] = Invalid(NonEmptyList.of("error"))
val invalid4 = "another error".invalidNel[Int]
(valid1, valid2, invalid3, invalid4).mapN { case (i1, i2, i3, i4) => i1 + i2 + i3 + i4 }
// res2: cats.data.ValidatedNel[String,Int] = Invalid(NonEmptyList(error, another error))

We first define a Valid value of 1, with a Valid type Int parameter, and an Invalid type NonEmptyList[String] parameter. Each error will be of a String type, and the NonEmptyList instance will force us to have at least one error when we produce an Invalid value. This usage is so common that cats provide a type alias called ValidatedNel in the cats.data package, as shown in the following code :

type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A]

Going back to our example, in the second line, we define a Valid value of 2 using a handy cats method called .validNel. When calling validNel, we have to pass the type of error, because in this case, the compiler does not have any information to infer it. In our case, the error type is String. The resulting type of valid2 is ValidatedNel[String, Int], which is an alias for Validated[NonEmptyList[String], Int].

In the third line, we compose the two valid values by putting them in a tuple and call mapN. The mapN phrase accepts an function that takes as many arguments as there are elements in the tuple. If all of the elements of the tuple are Valid values, f is called and its result will be wrapped in a Valid value. If any of the elements inside the tuple are Invalid values, then all the Invalid values are combined together and wrapped in an Invalid value.

We can observe that, when we compose valid1 and valid2, which are all Valid, mapN returns a Valid value. When we compose valid1, valid2, invalid3, and invalid4, mapN returns an Invalid value. This Invalid value wraps a NonEmptyList that contains the errors of invalid3 and invalid4.

We now know two mechanisms to represent the possibility of a failure:

  • Either with for...yield can be used to validate sequentially, stopping at the first error encountered
  • Validated with mapN can be used to validate in parallel, accumulating all the errors in NonEmptyList
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset