Skip to content

Instantly share code, notes, and snippets.

@xuwei-k
Last active December 26, 2015 04:59
Show Gist options
  • Save xuwei-k/7097510 to your computer and use it in GitHub Desktop.
Save xuwei-k/7097510 to your computer and use it in GitHub Desktop.
ScalaUtils vs Scalaz7
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.0.4"
scalaVersion := "2.10.3"
import scalaz._, Scalaz._
object Main extends App{
type ErrorMessage = String
def parseName(input: String): ValidationNel[ErrorMessage, String] = {
val trimmed = input.trim
if (!trimmed.isEmpty) Success(trimmed) else s""""${input}" is not a valid name""".failNel
}
def parseAge(input: String): ValidationNel[ErrorMessage, Int] = {
try {
val age = input.trim.toInt
if (age >= 0) Success(age) else s""""${age}" is not a valid age""".failNel
}
catch {
case _: NumberFormatException => s""""${input}" is not a valid integer""".failNel
}
}
case class Person(name: String, age: Int)
def parsePerson(inputName: String, inputAge: String): ValidationNel[ErrorMessage, Person] = {
val name = parseName(inputName)
val age = parseAge(inputAge)
(name |@| age) { Person(_, _) }
}
implicit val personEqual: Equal[Person] = Equal.equalA[Person]
implicit val personShow: Show[Person] = Show.showA[Person]
parsePerson("Bridget Jones", "29") assert_=== Success(Person("Bridget Jones", 29))
parsePerson("Bridget Jones", "") assert_=== """"" is not a valid integer""".failNel
parsePerson("Bridget Jones", "-29") assert_=== """"-29" is not a valid age""".failNel
parsePerson("", "") assert_=== Failure(NonEmptyList(""""" is not a valid name""", """"" is not a valid integer"""))
// List(parseAge("29"), parseAge("30"), parseAge("31")).combined
List(parseAge("29"), parseAge("30"), parseAge("31")).sequenceU assert_=== Success(List(29, 30, 31))
// List(parseAge("29"), parseAge("-30"), parseAge("31")).combined
List(parseAge("29"), parseAge("-30"), parseAge("31")).sequenceU assert_=== """"-30" is not a valid age""".failNel
// List(parseAge("29"), parseAge("-30"), parseAge("-31")).combined
List(parseAge("29"), parseAge("-30"), parseAge("-31")).sequenceU assert_=== Failure(NonEmptyList(""""-30" is not a valid age""", """"-31" is not a valid age"""))
// List("29", "30", "31").validatedBy(parseAge)
List("29", "30", "31").traverseU(parseAge) assert_=== Success(List(29, 30, 31))
// List("29", "-30", "31").validatedBy(parseAge)
List("29", "-30", "31").traverseU(parseAge) assert_=== """"-30" is not a valid age""".failNel
// List("29", "-30", "-31").validatedBy(parseAge)
List("29", "-30", "-31").traverseU(parseAge) assert_=== Failure(NonEmptyList(""""-30" is not a valid age""", """"-31" is not a valid age"""))
// parseName("Dude") zip parseAge("21")
parseName("Dude") tuple parseAge("21") assert_=== Success(("Dude", 21))
// parseName("Dude") zip parseAge("-21")
parseName("Dude") tuple parseAge("-21") assert_=== """"-21" is not a valid age""".failNel
// parseName("") zip parseAge("-21")
parseName("") tuple parseAge("-21") assert_=== Failure(NonEmptyList(""""" is not a valid name""", """"-21" is not a valid age"""))
def isRound(i: Int): ValidationNel[ErrorMessage, Int] =
if (i % 10 == 0) Success(i) else (i + " was not a round number").failNel
def isDivBy3(i: Int): ValidationNel[ErrorMessage, Int] =
if (i % 3 == 0) Success(i) else (i + " was not divisible by 3").failNel
type ValidationNelErr[+A] = ValidationNel[ErrorMessage, A]
val _isRound = Kleisli[ValidationNelErr, Int, Int](isRound)
val _isDievBy3 = Kleisli[ValidationNelErr, Int, Int](isDivBy3)
val f = (_isRound *> _isDievBy3).run _
parseAge("30") flatMap f assert_=== Success(30)
parseAge("33") flatMap f assert_=== "33 was not a round number".failNel
parseAge("20") flatMap f assert_=== "20 was not divisible by 3".failNel
parseAge("31") flatMap f assert_=== Failure(NonEmptyList("31 was not a round number", "31 was not divisible by 3"))
}
@bvenners
Copy link

Nice comparison. I wasn't aware Scalaz had the tuple method, so that's nice to learn. You can use traverseU for validatedBy. I'm not sure how to update a gist with git, so here's what that would look like:

// List("29", "30", "31").validatedBy(parseAge)
List("29", "30", "31").traverseU(parseAge) assert_=== Success(List(29, 30, 31))

// List("29", "-30", "31").validatedBy(parseAge)
List("29", "-30", "31").traverseU(parseAge) assert_=== """"-30" is not a valid age""".failNel

// List("29", "-30", "-31").validatedBy(parseAge)
List("29", "-30", "-31").traverseU(parseAge) assert_=== Failure(NonEmptyList(""""-30" is not a valid age""", """"-31" is not a valid age"""))

@xuwei-k
Copy link
Author

xuwei-k commented Oct 22, 2013

You can use traverseU for validatedBy

You're right! I just updated. thanks :)

@przemek-pokrywka
Copy link

I'm wondering what would be the best way to have both power of Scalaz (things compose easily) and the naming decisions taken in ScalaUtils - that is to have among others:

  • "Int Or ErrorMessage" (where the positive case comes first) instead of "Validation[ErrorMessage, Int]"
  • "Bad(error)" instead of "error.failNel"

A bunch of conversion functions between the corresponding concepts? A bunch of typealiases? Both?

@bvenners
Copy link

I would hope you could just define Scalaz type class instances for Or and Every if you wanted to use both libraries together. What specifically would you want to do? I.e., how you would want to compose things with Scalaz? Once you have some specific goals we could try and figure out what typeclasses would be needed for that, then just define them. It would be interesting to see if that would work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment