package scalaz.example import scalaz._ import _root_.java.lang.String import collection.immutable.List import collection.Traversable object ExampleValidation { def main(args: Array[String]) = run import Scalaz._ def run { // Constructing Validations failure[String, Int]("error") assert_≟ "error".fail[Int] success[String, Int](0) assert_≟ 0.success[String] validation[String, Int](Left("error")) assert_≟ "error".fail[Int] validation[String, Int](Right(0)) assert_≟ 0.success[String] // Extracting success or failure values val s: Validation[String, Int] = 1.success val f: Validation[String, Int] = "error".fail s.toOption assert_≟ some(1) s.fail.toOption assert_≟ none[String] f.toOption assert_≟ none[Int] f.fail.toOption assert_≟ some("error") // It is recommended to use fold rather than pattern matching: val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString) s match { case Success(a) => "success" case Failure(e) => "fail" } // Validation#| is analogous to Option#getOrElse (f | 1) assert_≟ 1 // Validation is a Monad, and can be used in for comprehensions. val k1 = for { i <- s j <- s } yield i + j k1.toOption assert_≟ Some(2) // The first failing sub-computation fails the entire computation. val k2 = for { i <- f j <- f } yield i + j k2.fail.toOption assert_≟ Some("error") // 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 the all success, a function can combine them into a Success. If any // of them fails, the individual errors are accumulated. // Combining validation errors using the String Semigroup. val k3 = (f <**> f){ _ + _ } k3.fail.toOption assert_≟ some("errorerror") // The String semigroup wasn't particularly useful. A better candidate is NonEmptyList. Below, we use // Validation#liftFailNel to convert from Validation[String, Int] to Validation[NonEmptyList[String], Int]. // The type alias ValidationNEL makes this more concise. val fNel: ValidationNEL[String, Int] = f.liftFailNel // Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor. val k4 = (fNel <**> fNel){ _ + _ } k4.fail.toOption assert_≟ some(nel1("error", "error")) person parseNumbers } /** * See Automated Validation with Applicatives and Semigroups <a href="http://blog.tmorris.net/automated-validation-with-applicatives-and-semigroups-for-sanjiv/">Part 1</a> * and <a href="http://blog.tmorris.net/automated-validation-with-applicatives-and-semigroups-part-2-java/">Part 2</a> */ def person { sealed trait Name extends NewType[String] object Name { def apply(s: String): Validation[String, Name] = if (s.headOption.exists(_.isUpper)) (new Name {val value = s}).success else "Name must start with a capital letter".fail } sealed trait Age extends NewType[Int] object Age { def apply(a: Int): Validation[String, Age] = if (0 to 130 contains a) (new Age {val value = a}).success else "Age must be in range".fail } case class Person(name: Name, age: Age) def mkPerson(name: String, age: Int) = (Name(name).liftFailNel ⊛ Age(age).liftFailNel){ (n, a) => Person(n, a)} mkPerson("Bob", 31).isSuccess assert_≟ true mkPerson("bob", 131).fail.toOption assert_≟ some(nel1("Name must start with a capital letter", "Age must be in range")) } def parseNumbers { def only[A](as: Traversable[A]): Validation[String, A] = { val firstTwo = as.take(2).toSeq validation((firstTwo.size != 1) either "required exactly one element" or firstTwo.head) } def empty[A](as: Traversable[A]): Validation[String, Unit] = validation(!as.isEmpty either "expected an empty collection" or ()) // Combine two validations with the Validation Applicative Functor, using only the success // values from the first. val x: ValidationNEL[String, Int] = only(Seq(1)).liftFailNel <* empty(Seq.empty).liftFailNel x assert_≟ 1.successNel[String] val badInput = """42 |aasf |314 |xxx""".stripMargin parse(badInput) assert_≟ nel1("java.lang.NumberFormatException: For input string: \"aasf\"", "java.lang.NumberFormatException: For input string: \"xxx\"").fail[List[Int]] val validInput = """42 |314""".stripMargin parse(validInput) assert_≟ List(42, 314).successNel[String] } /** * Parse text containing a list of integers, each on a separate line. */ def parse(text: String): ValidationNEL[String, List[Int]] = { val lines = text.lines.toList def parseInt(s: String): ValidationNEL[String, Int] = { val projection: FailProjection[String, Int] = s.parseInt.fail ∘ (_.toString) // todo this can't be inferred if Pure is invariant. Why not? projection.lift[NonEmptyList, String] } val listVals: List[ValidationNEL[String, Int]] = lines.map(parseInt(_)) // Sequence the List using the Validation Applicative Functor. listVals.sequence[PartialApply1Of2[ValidationNEL, String]#Apply, Int] } }