Skip to content

Instantly share code, notes, and snippets.

@manjuraj
Last active June 25, 2018 21:01
Show Gist options
  • Save manjuraj/2e27a1b66e7e0be697ca to your computer and use it in GitHub Desktop.
Save manjuraj/2e27a1b66e7e0be697ca to your computer and use it in GitHub Desktop.
scalaz validation
//
// Validation[E, A] represents either:
// - Success[A]
// - Failure[E]
//
// Isomporphic to scala.Either[E, A] and scalaz.\/[E, A]
//
// Unlike \/[E, A], Validation is not a Monad, but an Applicative
// Functor. So if you want to use it as a Monad you can convert back
// and forth using the `validation` and `disjunction` method
//
// The motivation for a Validation is to provide the instance Applicative
// that accumulate failures through a scalaz.Semigroup[E]
//
// scalaz.NonEmptyList is commonly chosen as a type constructor for
// the type E. As a convenience, an alias scalaz.ValidationNel[E] is
// provided as a shorthand for scalaz.Validation[NonEmptyList[E]], and
// a method Validation#toValidationNel converts Validation[E] to
// ValidationNel[E]
//
//
// \/[A, B] and Validation[A, B] are isomorphic, but Validation
// accumulates errors (lefts) while \/ "fails fast"
//
import scalaz._
import scalaz.syntax.validation._
scala> val e = "error".failure[Int]
res1: scalaz.Validation[String,Int] = Failure(error)
scala> val s = 1.success[String]
res2: scalaz.Validation[String,Int] = Success(1)
scala> e.getOrElse(2)
res3: Int = 2
scala> e | 2
res4: Int = 2
scala> e.fold(_ => 2, _ * 2)
res7: Int = 2
scala> e.toOption
res10: Option[Int] = None
scala> s.map(_ * 100)
res12: scalaz.Validation[String,Int] = Success(100)
scala> val e = Validation.failure[String, Int]("error")
e: scalaz.Validation[String,Int] = Failure(error)
scala> val s = Validation.success[String, Int](1)
s: scalaz.Validation[String,Int] = Success(1)
//
// Validation is also an Applicative Functor, if the type of
// the error side of the validation is a Semigroup. A number
// of computations are tried. If they are all success, a function
// can combine them into a Success. If any of them fails, the
// individual errors are accumulated.
//
// Here, we combine validation errors using the String Semigroup
//
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.apply._
import scalaz.std.string._
scala> val e = "error".failure[Int]
res1: scalaz.Validation[String,Int] = Failure(error)
scala> val s = 1.success[String]
res2: scalaz.Validation[String,Int] = Success(1)
scala> (e |@| e) {_ + _}
res0: scalaz.Unapply[scalaz.Apply,scalaz.Validation[String,Int]]{type M[X] = scalaz.Validation[String,X]; type A = Int}#M[Int] = Failure(errorerror)
scala> val es = e.toValidationNel
res4: scalaz.ValidationNel[String,Int] = Failure(NonEmptyList(error))
// Use the NonEmptyList semigroup to accumulate errors
scala> (es |@| es){_ + _}
res5: scalaz.Unapply[scalaz.Apply,scalaz.ValidationNel[String,Int]]{type M[X] = scalaz.ValidationNel[String,X]; type A = Int}#M[Int] = Failure(NonEmptyList(error, error))
//
// Isomporphism: Validation[E, A] <> \/[E, A]
//
import scalaz._
import scalaz.std.either._
import scalaz.syntax.either._
import scalaz.syntax.validation._
// \/[E, A] => Validation[E, A]
scala> 1.right[String].validation
res3: scalaz.Validation[String,Int] = Success(1)
// Validation[E, A] => \/[E, A]
scala> 1.right[String].validation.disjunction
res4: scalaz.\/[String,Int] = \/-(1)
scala> 1.success[String].disjunction
res5: scalaz.\/[String,Int] = \/-(1)
// Run a disjunction function and back to validation again
scala> 1.success[String].disjunctioned(identity)
res13: scalaz.Validation[String,Int] = Success(1)
scala> 1.success[String] @\/ (identity)
res15: scalaz.Validation[String,Int] = Success(1)
//
// Accumulate errrors
//
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.apply._
import scalaz.std.AllInstances._
scala> ("event 1 ok".success[String] |@| "event 2 failed!".failure[String]) {_ + _}
res0: scalaz.Unapply[scalaz.Apply,scalaz.Validation[String,String]]{type M[X] = scalaz.Validation[String,X]; type A = String}#M[String] = Failure(event 2 failed!)
scala> ("event 1 ok".success[String] |@| "event 2 failed!".failure[String] |@| "event 3 failed".failure[String]) {_ + _ + _}
res2: scalaz.Unapply[scalaz.Apply,scalaz.Validation[String,String]]{type M[X] = scalaz.Validation[String,X]; type A = String}#M[String] = Failure(event 2 failed!event 3 failed)
scala> ("event 1 ok".success[String] +++ "event 2 failed!".failure[String] +++ "event 3 failed".failure[String])
res4: scalaz.Validation[String,String] = Failure(event 2 failed!event 3
//
// ValidationNel[E, A] = Validation[NonEmptyList[E], A]
//
import scalaz._
import scalaz.syntax.validation._
scala> "error".failureNel
res21: scalaz.ValidationNel[String,Nothing] = Failure(NonEmptyList(error))
scala> "ok".successNel
res22: scalaz.ValidationNel[Nothing,String] = Success(ok)
//
// Validation => ValidatoinNel
//
scala> "ok".success.toValidationNel
res23: scalaz.ValidationNel[Nothing,String] = Success(ok)
scala> "error".failure.toValidationNel
res24: scalaz.ValidationNel[String,Nothing] = Failure(NonEmptyList(error))
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.apply._
import scalaz.std.AllInstances._
scala> ("event 1 ok".successNel[String] +++ "event 2 failed!".failureNel[String] +++ "event 3 failed".failureNel[String])
res1: scalaz.Validation[scalaz.NonEmptyList[String],String] = Failure(NonEmptyList(event 2 failed!, event 3 failed))
//
// Option[A] => Validation[E, A]
// toSuccess[E]
// toFailure[B]
//
import scalaz._
import syntax.std.option._
scala> Some(10).toSuccess("message")
res10: scalaz.Validation[String,Int] = Success(10)
scala> None.toSuccess("message")
res11: scalaz.Validation[String,A] = Failure(message)
//
// Example (1) of Accumulating errors with Validation
//
import scalaz.{ Category => _, _ }
import scalaz.syntax.apply._
import scalaz.syntax.std.option._
import scalaz.syntax.validation._
// Data model
case class User(name: String)
case class Category(user: User, parent: Category, name: String, desc: String)
// Validation method that Accumulates errors
def nonNull[A](a: A, msg: String): ValidationNel[String, A] = {
Option(a).toSuccess(msg).toValidationNel
}
// ValidationNel[String, A] is an applicative functor and we are lifting
// Category into it
def buildCategory(user: User, parent: Category, name: String, desc: String) = (
nonNull(user, "User is mandatory for a normal category") |@|
nonNull(parent, "Parent category is mandatory for a normal category") |@|
nonNull(name, "Name is mandatory for a normal category") |@|
nonNull(desc, "Description is mandatory for a normal category")
)(Category.apply)
scala> buildCategory(User("mary"), null, null, "Some category.")
res0: scalaz.Unapply[scalaz.Apply,scalaz.ValidationNel[String,User]]{type M[X] = scalaz.ValidationNel[String,X]; type A = User}#M[Category] = Failure(NonEmptyList(Parent category is mandatory for a normal category, Name is mandatory for a normal category))
// Validation method that Fails fast with \/
def nonNull[A](a: A, msg: String): \/[String, A] = {
Option(a) \/> msg
}
scala> buildCategory(User("mary"), null, null, "Some category.")
res2: scalaz.Unapply[scalaz.Apply,scalaz.\/[String,User]]{type M[X] = scalaz.\/[String,X]; type A = User}#M[Category] = -\/(Parent category is mandatory for a normal category)
//
// Example (2) of Accumulating errors with Validation
//
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.std.boolean._
import scalaz.std.boolean._
import scalaz.syntax.apply._
class Name private(val name: String)
object Name {
def apply(n: String): Validation[String, Name] = {
val valid = n.headOption.exists(_.isUpper)
valid ? (new Name(n)).success[String] | "name must start with a capital letter".failure[Name]
}
}
class Age private(val age: Int)
object Age {
def apply(a: Int): Validation[String, Age] = {
(a > 0 && a <= 130) ? (new Age(a)).success[String] | "age must be in range".failure[Age]
}
}
class Person private(name: Name, age: Age)
object Person {
def apply(n: String, a: Int): ValidationNel[String, Person] = {
(Name(n).toValidationNel |@| Age(a).toValidationNel) { (n, a) => new Person(n, a) }
}
}
scala> Person("Bob", 31)
res0: scalaz.ValidationNel[String,Person] = Success(Person@2c372ce1)
scala> Person("bob", 31)
res1: scalaz.ValidationNel[String,Person] = Failure(NonEmptyList(name must start with a capital letter))
//
// Example (3) of Accumulating errors with Validation
//
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.std.boolean._
import scalaz.std.boolean._
import scalaz.syntax.apply._
def empty[A](as: Traversable[A]): Validation[String, Unit] = {
!as.isEmpty ? "expected an empty collection".failure[Unit] | ().success[String]
}
def only[A](as: Traversable[A]): Validation[String, A] = {
val firstTwo = as.take(2).toSeq
(firstTwo.size != 1) ? "required exactly on elemenent".failure[A] | firstTwo.head.success[String]
}
//
// combine two validations with the validation applicative
// using only the success value from the first
//
scala> only(Seq(1)).toValidationNel <* empty(Seq.empty).toValidationNel
res4: scalaz.Unapply[scalaz.Apply,scalaz.ValidationNel[String,Int]]{type M[X] = scalaz.ValidationNel[String,X]; type A = Int}#M[Int] = Success(1)
//
// Example (3) of Accumulating errors with Validation
//
import scalaz._
import scalaz.syntax.validation._
import scalaz.syntax.std.string._
import scalaz.std.string._
import scalaz.syntax.std.boolean._
import scalaz.std.boolean._
import scalaz.syntax.apply._
import scalaz.syntax.traverse._
import scalaz.std.list._
// Parse text containing a list of integers, each on a separate line
def parse(text: String): ValidationNel[String, List[Int]] = {
def parseInt(s: String): ValidationNel[String, Int] = {
s.parseInt.leftMap(_.toString).toValidationNel
}
val lines: List[String] = text.lines.toList
val listVals: List[ValidationNel[String, Int]] = lines.map(parseInt)
// Sequence the List using the Validation Applicative Functor
listVals.sequence[({type λ[α]=ValidationNel[String, α]})#λ, Int]
}
val badInput =
"""42
|aasf
|314
|xxx""".stripMargin
scala> parse(badInput)
res0: scalaz.ValidationNel[String,List[Int]] = Failure(NonEmptyList(java.lang.NumberFormatException: For input string: "aasf", java.lang.NumberFormatException: For input string: "xxx"))
val validInput =
"""42
|314""".stripMargin
scala> parse(validInput)
scala> parse(validInput)
res1: scalaz.ValidationNel[String,List[Int]] = Success(List(42, 314))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment