I use a few Scalaz Validaiton methods for chaining validations together: flatMap, |@|, and map.
The flatMap method is used to short circuit a validation. For instance, if a value is null or empty, there is no reason to validate whether or not the value has exceeded a max length.
The |@| is used to collect all validations.
The map method is used to convert a valid result into another valid result, useful during context change. Also failProject.map(_).validation is used to convert a failure into another type of failure. I use this, for instance, to change a Validation error which is a general error into a JSON formatted error which I can return from a web service.
I am always finding myself having to relearn what some of the scalaz Validation methods do, so here they are. This is my interpretation of what each of the methods in scalaz that are relative to Validation. The validation class can be found here:https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Validation.scala . Also note that some methods come from MAB which can be found https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/MAB.scala .
Assume the following classes and validations are defined:
case class User(name: String) case class SecretCode def notEmpty: (String) => Validation[NonEmptyList[String], String] = { (s: String) => { if (s.size == 0) "must not be empty".wrapNel.fail else s.success } } def maxLength(max: Int): (String) => Validation[NonEmptyList[String], String] = { (s: String) => { if (s.size > max) ("The maximum length is" + max).wrapNel.fail else s.success } } def minLength(min: Int): (String) => Validation[String, String] = { (s: String) => { if (s.size < min) ("The minimum lenth is" + min).fail else s.success } } def checkAccess: (String) => Validation[NonEmptyList[String], SecretCode] = { (u: String) => { if (u == "Fred") "Fred is not allowed to know the Secret Code".wrapNel.fail else SecretCode().success } }
def fold[X](failure: E => X = identity[E] _, success: A => X = identity[A] _): X
Folds the validation into one value. This is a Catamorphism.
scala> notEmpty("").fold(f => None, s => User(s)) res3: Product with Serializable = None scala> notEmpty("John").fold(f => None, s => User(s)) res4: Product with Serializable = User(John)
def map[B](f: A => B): Validation[E, B]
If this is a failure, this transformation will not be applied. Note that resulting success object has changes to whatever the result of the function f is, but the failure stays the same.
scala> notEmpty("").map(User(_)) res5: scalaz.Validation[scalaz.NonEmptyList[String],User] = Failure(NonEmptyList(must not be empty)) scala> notEmpty("John").map(User(_)) res6: scalaz.Validation[scalaz.NonEmptyList[String],User] = Success(User(John))
def foreach[U](f: A => U): Unit
If a Success, applies the function f to the success object. Otherwise does nothing.
scala> notEmpty("").foreach(x => println("Good Name:'" + x + "'") ) scala> notEmpty("John").foreach(x => println("Good Name:'" + x + "'") ) Good Name:'John'
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B]
If the current Validation is a Success, then flatMap performs a new validation specified by function f.
If the current Validation is a Failure, then flatMap wraps the failure object in the new Validation type. In both cases
the failure type basically stays the same, but the success type changes.
If there is a chain of flatmapped validations, the first failure with be the one reported and the subsequent validations will not run.
scala> notEmpty("").flatMap(checkAccess) res15: scalaz.Validation[scalaz.NonEmptyList[String],SecretCode] = Failure(NonEmptyList(must not be empty)) scala> notEmpty("Fred").flatMap(checkAccess) res16: scalaz.Validation[scalaz.NonEmptyList[String],SecretCode] = Failure(NonEmptyList(Fred is not allowed to know the Secret Code)) scala> notEmpty("John").flatMap(checkAccess) res17: scalaz.Validation[scalaz.NonEmptyList[String],SecretCode] = Success(SecretCode())
def either : Either[E, A]
Turns the Validation[E,A] into an Either[E,A]
scala> notEmpty("").either match { | case Left(f) => "FAIL" | case Right(s) => "SUCCESS" | } res7: java.lang.String = FAIL scala> notEmpty("John").either match { | case Left(f) => "FAIL" | case Right(s) => "SUCCESS" | } res8: java.lang.String = SUCCESS
def isSuccess : Boolean
Returns true if the Validation is a success object, false otherwise
scala> notEmpty("").isSuccess res18: Boolean = false scala> notEmpty("John").isSuccess res19: Boolean = true
def isFailure : Boolean
Returns true if the Validation is a failure object, false otherwise.
scala> notEmpty("").isFailure res20: Boolean = true scala> notEmpty("John").isFailure res21: Boolean = false
def toOption : Option[A]
If the Validation is a success, returns the success object wrapped in some. Otherwise it returns none.
scala> notEmpty("").toOption res22: Option[String] = None scala> notEmpty("Fred") res23: scalaz.Validation[scalaz.NonEmptyList[String],String] = Success(Fred)
def >>*<<[EE >: E: Semigroup, AA >: A: Semigroup](x: Validation[EE, AA]): Validation[EE, AA]
If one of the two validations is a success, it returns the successful one. If both are failures it appends the failures together. If both are successes, the successes are appended to each other. A list may be a better example for concatenating a success result, but a NonEmptyList is a nice failure accumulator. Not that the append is done with the scalaz ? operator. TODO: find out where this is defined and elaborate.
scala> notEmpty("Fred") >>*<< notEmpty("John") res24: scalaz.Validation[scalaz.NonEmptyList[String],String] = Success(FredJohn) scala> notEmpty("Fred") >>*<< notEmpty("") res25: scalaz.Validation[scalaz.NonEmptyList[String],String] = Success(Fred) scala> notEmpty("") >>*<< notEmpty("John") res26: scalaz.Validation[scalaz.NonEmptyList[String],String] = Success(John) scala> notEmpty("") >>*<< notEmpty("") res27: scalaz.Validation[scalaz.NonEmptyList[String],String] = Failure(NonEmptyList(must not be empty, must not be empty))
def fail : FailProjection[E, A] = new FailProjection[E, A]
Use map (defined in MA) to convert a Failure from one type to another.
def lift[M[_]: Pure, AA >: A]: Validation[E, M[AA]]
If the validation is a success, it wraps the success object into the specified container. Lifting to a List or Option seems useful, but I'm not sure what other options there are.
scala> notEmpty("John").lift[Option, String] res32: scalaz.Validation[scalaz.NonEmptyList[String],Option[String]] = Success(Some(John)) scala> notEmpty("").lift[Option, String] res33: scalaz.Validation[scalaz.NonEmptyList[String],Option[String]] = Failure(NonEmptyList(must not be empty)) scala> notEmpty("John").lift[List, String] res34: scalaz.Validation[scalaz.NonEmptyList[String],List[String]] = Success(List(John))
def liftFailNel: Validation[NonEmptyList[E], A] = fail.liftNel
Wraps the failure value in a NonEmptyList. I tend to define my return type to be Validation[NonEmptyList[E], A], however if you defined a return type to be Validation[E,A] you could wrap the result in liftFailNel to have the same type.
scala> val five = minLength(5) five: String => scalaz.Validation[String,String] =scala> five("1234").liftFailNel res35: scalaz.Validation[scalaz.NonEmptyList[String],String] = Failure(NonEmptyList(The minimum lenth is5)) scala> five("123456").liftFailNel res36: scalaz.Validation[scalaz.NonEmptyList[String],String] = Success(123456)
def |||[AA >: A](f: E => AA): AA
Like getOrElse on option, but the default value takes an argument. If success, returns the success object. If failure, uses the specified function to produce a value.
scala> notEmpty("John") ||| {x => "Anonymous: where failure was " + x.list.mkString} res50: String = John scala> notEmpty("") ||| {x => "Anonymous: where failure was " + x.list.mkString} res51: String = Anonymous: where failure was must not be empty
def |[AA >: A](f: => AA): AA
Like getOrElse, provide a default value if failure.
scala> notEmpty("John") | User("Anonymous") res42: java.io.Serializable = John scala> notEmpty("") | User("Anonymous") res43: java.io.Serializable = User(Anonymous)
def forall(f: A => Boolean): Boolean
Checks to see if the conditional is true for the success object. If validation is a Failure, it is considered true.
Defined of MAB
def :->[D](g: B => D)(implicit b: Bifunctor[M]): M[A, D]
This is essentially the same thing as map.
scala> notEmpty("") :-> {User(_)} res61: scalaz.Validation[scalaz.NonEmptyList[String],User] = Failure(NonEmptyList(must not be empty)) scala> notEmpty("Fred") :-> {User(_)} res62: scalaz.Validation[scalaz.NonEmptyList[String],User] = Success(User(Fred))
def <-:[C](f: A => C)(implicit b: Bifunctor[M]): M[C, B]
This is essentially the same as ||| but with different syntax.
scala> {x: NonEmptyList[String] => x.list.mkString} <-: notEmpty("") res68: scalaz.Validation[String,String] = Failure(must not be empty) scala> {x: NonEmptyList[String] => x.list.mkString} <-: notEmpty("Fred") res69: scalaz.Validation[String,String] = Success(Fred)
Defined on ApplicativeBuilder
def |@|[C](c: M[C])
Allows one to chain validation, collecting all the failures. To do this you can apply the result to a partial function. If there is at least one failure, then the result will be a collection of all the failures.