Last active
June 25, 2018 21:01
-
-
Save manjuraj/2e27a1b66e7e0be697ca to your computer and use it in GitHub Desktop.
scalaz validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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