liveBook · Manning

Validated

As explained in Functional Programming in Scala, Second Edition

Validated is an error-handling data type that represents successful or failed computations and can accumulate errors across combined failures.

Validated represents either a successful value or a failed computation. Unlike Either, combining multiple failed computations preserves all their errors instead of stopping at one failure. section 4.6

A Validated[E, A] value has a successful Valid case and an Invalid case containing errors. Naming this behavior gives error accumulation a reusable home rather than scattering it across separate -All functions. section 4.4.2

The core operations make the accumulation rule explicit:

  • map transforms a successful value and preserves an existing Invalid value. section 4.4.2
  • map2 combines two successful values, propagates a single failure, or combines both failures. section 4.4.2
  • traverse and sequence apply the same rule across lists of computations. section 4.4.2

Combining failures

Combining Invalid(es1) with Invalid(es2) produces Invalid(es1 ++ es2). A generalized Validated replaces List[E] with an error type E and accepts combineErrors: (E, E) => E, allowing errors to use another structure. section 4.4.2

enum Validated[+E, +A]:
  case Valid(get: A)
  case Invalid(errors: List[E])
 
  def toEither: Either[List[E], A] =
    this match
      case Valid(a) => Either.Right(a)
      case Invalid(es) => Either.Left(es)
 
  def map[B](f: A => B): Validated[E, B] =
    this match
      case Valid(a) => Valid(f(a))
      case Invalid(es) => Invalid(es)
 
  def map2[EE >: E, B, C](
    b: Validated[EE, B])(
    f: (A, B) => C
  ): Validated[EE, C] =
    (this, b) match
      case (Valid(aa), Valid(bb)) => Valid(f(aa, bb))
      case (Invalid(es), Valid(_)) => Invalid(es)
      case (Valid(_), Invalid(es)) => Invalid(es)
      case (Invalid(es1), Invalid(es2)) => Invalid(es1 ++ es2)
 
object Validated:
  def fromEither[E, A](e: Either[List[E], A]): Validated[E, A] =
    e match
      case Either.Right(a) => Valid(a)
      case Either.Left(es) => Invalid(es)
 
  def traverse[E, A, B](
    as: List[A], f: A => Validated[E, B]
  ): Validated[E, List[B]] =
    as.foldRight(Valid(Nil): Validated[E, List[B]])((a, acc) =>
      f(a).map2(acc)(_ :: _))
 
  def sequence[E, A](vs: List[Validated[E, A]]): Validated[E, List[A]] =
    traverse(vs, identity)

Inspect the complete data type and its conversion, mapping, combination, and traversal operations. section 4.4.2

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Defines constructors, conversions, map2, traversal, and customizable error combination, stopping short of application-specific validation.
Examples
Demonstrates Either conversion, map2 failure accumulation, generalized combineErrors, traverse, and sequence.
What it assumes
Assumes familiarity with Option, Either, Try, higher-order functions, and algebraic data types.
What you can do after
Apply Validated through map, map2, traverse, sequence, and conversions to compose computations without exceptions.
How it connects
Contrasts with Option, Either, and Try, and uses map, flatMap, traversal, and algebraic data types.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.