Created
November 28, 2013 19:06
-
-
Save nlinker/7696815 to your computer and use it in GitHub Desktop.
My update for scalaz.Validation example out there
This file contains hidden or 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
| package com.tapad.dfe | |
| import scalaz._ | |
| import java.lang.String | |
| import collection.immutable.List | |
| import collection.Traversable | |
| import scala.None | |
| import scalaz.syntax.{IdOps, ToValidationOps} | |
| object ExampleValidation extends ToValidationOps with NonEmptyListInstances { | |
| def main(args: Array[String]) = run() | |
| implicit class AssertWrapper(val o: Any) extends AnyVal { | |
| def must_==(p: Any) = if (o != p) throw new Exception() | |
| } | |
| def plusStr(a: String, b: => String): String = a + b | |
| def plusInt(a: Int, b: => Int): Int = a + b | |
| def run() { | |
| // Constructing Validations | |
| Failure[String, Int]("error") must_== "error".failure[Int] | |
| Success[String, Int](0) must_== 0.success[String] | |
| // Validation[String, Int](Left("error")) must_== "error".fail[Int] | |
| // Validation[String, Int](Right(0)) must_== 0.success[String] | |
| // Extracting success or failure values | |
| val s: Validation[String, Int] = 1.success | |
| val f: Validation[String, Int] = "error".fail | |
| s.toOption must_== Some(1) | |
| f.toOption must_== None | |
| // 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) must_== 1 | |
| // Validation is a Monad, and can be used in for comprehensions. | |
| val k1 = for { | |
| i <- s | |
| j <- s | |
| } yield i + j | |
| k1.toOption must_== Some(2) | |
| // The first failing sub-computation fails the entire computation. | |
| val k2 = for { | |
| i <- f | |
| j <- f | |
| } yield i + j | |
| k2.toOption must_== None | |
| // 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. | |
| implicit val intSg = Semigroup.instance(plusInt) | |
| implicit val strSg = Semigroup.instance(plusStr) | |
| val k3 = f +++ f | |
| k3.toEither must_== Left("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 must_== some(nel1("error", "error")) | |
| // | |
| // person | |
| // parseNumbers | |
| val nel1 = "event 1 ok".successNel[String] | |
| val nel2 = "event 2 fail".failureNel[String] | |
| val nel3 = "event 3 fail".failureNel[String] | |
| val all = nel1 +++ nel2 +++ nel3 | |
| all.toEither must_== Left(NonEmptyList("event 2 fail", "event 3 fail")) | |
| println(all.toEither) | |
| } | |
| // /** | |
| // * 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 must_== true | |
| // mkPerson("bob", 131).fail.toOption must_== 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 must_== 1.successNel[String] | |
| // | |
| // val badInput = """42 | |
| // |aasf | |
| // |314 | |
| // |xxx""".stripMargin | |
| // parse(badInput) must_== 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) must_== 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] | |
| // } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment