Skip to content

Instantly share code, notes, and snippets.

@nlinker
Created November 28, 2013 19:06
Show Gist options
  • Save nlinker/7696815 to your computer and use it in GitHub Desktop.
Save nlinker/7696815 to your computer and use it in GitHub Desktop.
My update for scalaz.Validation example out there
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