Scala Validation Accumulation
Validation accumulation is the practice of collecting every problem in an input. It reports all of them instead of stopping at the first one.
One check returns a list with zero or one problem
An empty name gives a list with one problem. A good name gives an empty list.
def nameErrors(name: String): List[String] =
if name.isEmpty then List("name is empty") else Nil
@main def main(): Unit =
println(nameErrors(""))
println(nameErrors("Ana"))Three checks join into one list in fixed order
The three check lists join with ++. The printed list shows all three problems, and its length proves the count.
def nameErrors(name: String): List[String] =
if name.isEmpty then List("name is empty") else Nil
def ageErrors(age: Int): List[String] =
if age < 0 then List("age is negative") else Nil
def emailErrors(email: String): List[String] =
if email.contains("@") then Nil else List("email needs @")
@main def main(): Unit =
val errors = nameErrors("") ++ ageErrors(-3) ++ emailErrors("ana.example")
println(errors)
println(errors.length)Either carries the full list or the good value
The good input folds to its ok message. The bad input folds to all its problems joined together.
def nameErrors(name: String): List[String] =
if name.isEmpty then List("name is empty") else Nil
def ageErrors(age: Int): List[String] =
if age < 0 then List("age is negative") else Nil
def validate(name: String, age: Int): Either[List[String], String] =
val errors = nameErrors(name) ++ ageErrors(age)
if errors.isEmpty then Right(name) else Left(errors)
@main def main(): Unit =
println(validate("Ana", 30).fold(errs => "errors: " + errs.mkString("; "), ok => "ok: " + ok))
println(validate("", -1).fold(errs => "errors: " + errs.mkString("; "), ok => "ok: " + ok))Example
def nameErrors(name: String): List[String] =
if name.isEmpty then List("name is empty") else Nil
def ageErrors(age: Int): List[String] =
if age < 0 then List("age is negative") else Nil
def emailErrors(email: String): List[String] =
if email.contains("@") then Nil else List("email needs @")
def validate(name: String, age: Int, email: String): Either[List[String], String] =
val errors = nameErrors(name) ++ ageErrors(age) ++ emailErrors(email)
if errors.isEmpty then Right("welcome " + name) else Left(errors)
@main def main(): Unit =
println(validate("", -2, "bob.example").fold(errs => "errors: " + errs.mkString("; "), ok => ok))
println(validate("Bob", 25, "bob@example.com").fold(errs => "errors: " + errs.mkString("; "), ok => ok))Key points
- Error list: problems are collected in a List, in a fixed order.
- Fixed order: the same input always gives the same list in the same order.
- Empty list: an empty error list means the input passed every check.
- Either result: Either with a List on the left carries all problems or the good value on the right.
- Combine checks: small check functions each return a list, and the lists join with ++.
In this example, three small check functions each return a list of problems. The validate function joins the three lists in a fixed order. A bad signup prints all three problems at once, and a good signup prints its welcome message.
Note
a Set of errors would print in no guaranteed order, so this course never prints one.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Scastie
Exercise
A signup has three problems. What does validation accumulation return?