Scalaz Validation: my personal cheat sheet

On April 25, 2012, in scalaz, Uncategorized, by OleTraveler

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>