Skip to content

Instantly share code, notes, and snippets.

@mbektimirov
Forked from oxbowlakes/3nightclubs.scala
Created May 11, 2012 15:41

Revisions

  1. @oxbowlakes oxbowlakes revised this gist Jul 11, 2011. 1 changed file with 9 additions and 0 deletions.
    9 changes: 9 additions & 0 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -169,6 +169,15 @@ object GayBar extends Nightclub {

    }

    object Test3 {
    import GayBar._

    def main(args: Array[String]) {
    costToEnter(Person(Gender.Male, 59, Set("Jeans"), Sobriety.Paralytic))) //Failure(NonEmptyList(Too Old!, Smarten Up!, Sober Up!))
    costToEnter2(Person(Gender.Male, 59, Set("Jeans"), Sobriety.Paralytic))) //Failure(NonEmptyList(Too Old!, Smarten Up!, Sober Up!))
    }
    }

    /**
    * As always; the point is that our validation functions are "static";
    * we do not need to change the way they have been coded because we want to combine them in different ways
  2. @oxbowlakes oxbowlakes revised this gist Jul 11, 2011. 1 changed file with 3 additions and 9 deletions.
    12 changes: 3 additions & 9 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -155,22 +155,16 @@ object GayBar extends Nightclub {

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    (checks map {(_ : (Person => Validation[String, Person])).apply(p).liftFailNel}).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    }
    (checks map {(_ : (Person => Validation[String, Person])).apply(p).liftFailNel}).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map {
    case c :: _ => c.age + 1.5D
    }
    }

    //Interestingly, as traverse is basically map + sequence, we can reduce this even further

    def costToEnter2(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks.traverse[({type l[a] = ValidationNEL[String, a]})#l, Person](_ andThen (_.liftFailNel) apply p) map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    }
    }
    checks.traverse[({type l[a] = ValidationNEL[String, a]})#l, Person](_ andThen (_.liftFailNel) apply p) map { case c :: _ => c.age + 1.5D }
    }

    }
  3. @oxbowlakes oxbowlakes revised this gist Jul 11, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -166,7 +166,7 @@ object GayBar extends Nightclub {

    def costToEnter2(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks.traverse[({type l[a] = ValidationNEL[String, a]})#l, Person](_ andThen ((_: Validation[String, Person]).liftFailNel) apply p) map { xs =>
    checks.traverse[({type l[a] = ValidationNEL[String, a]})#l, Person](_ andThen (_.liftFailNel) apply p) map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    }
  4. @oxbowlakes oxbowlakes revised this gist Jul 11, 2011. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -161,6 +161,18 @@ object GayBar extends Nightclub {
    }
    }
    }

    //Interestingly, as traverse is basically map + sequence, we can reduce this even further

    def costToEnter2(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks.traverse[({type l[a] = ValidationNEL[String, a]})#l, Person](_ andThen ((_: Validation[String, Person]).liftFailNel) apply p) map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    }
    }
    }

    }

    /**
  5. @oxbowlakes oxbowlakes revised this gist Jul 9, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@
    *
    * (In which we will see how to let the type system help you handle failure)...
    *
    * First let's define a domain. (All the following requires scala 2.8.x and scalaz 5.0)
    * First let's define a domain. (All the following requires scala 2.9.x and scalaz 6.0)
    */

    import scalaz._
  6. @oxbowlakes oxbowlakes revised this gist Jul 8, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -154,7 +154,7 @@ object GayBar extends Nightclub {
    p.success

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks : List[Person => Validation[String, Person]] = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    (checks map {(_ : (Person => Validation[String, Person])).apply(p).liftFailNel}).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
  7. @oxbowlakes oxbowlakes revised this gist Jul 8, 2011. 1 changed file with 44 additions and 42 deletions.
    86 changes: 44 additions & 42 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,15 @@
    /**
    * Part Zero : 10:15 Saturday Night
    *
    *
    * (In which we will see how to let the type system help you handle failure)...
    *
    *
    * First let's define a domain. (All the following requires scala 2.8.x and scalaz 5.0)
    */

    import scalaz._
    import Scalaz._

    object Sobriety extends Enumeration { val Sober, Tipsy, Drunk, Paralytic, Unconscious = Value }
    object Sobriety extends Enumeration { val Sober, Tipsy, Drunk, Paralytic, Unconscious = Value }

    object Gender extends Enumeration { val Male, Female = Value }

    @@ -20,29 +20,29 @@ case class Person(gender : Gender.Value, age : Int, clothes : Set[String], sobri
    */
    trait Nightclub {

    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    = if (p.age < 18)
    "Too Young!".fail
    else if (p.age > 40)
    "Too Old!".fail
    else
    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    = if (p.age < 18)
    "Too Young!".fail
    else if (p.age > 40)
    "Too Old!".fail
    else
    p.success

    //Second CHECK
    def checkClothes(p : Person) : Validation[String, Person]
    = if (p.gender == Gender.Male && !p.clothes("Tie"))
    "Smarten Up!".fail
    else if (p.gender == Gender.Female && p.clothes("Trainers"))
    "Wear high heels".fail
    else
    def checkClothes(p : Person) : Validation[String, Person]
    = if (p.gender == Gender.Male && !p.clothes("Tie"))
    "Smarten Up!".fail
    else if (p.gender == Gender.Female && p.clothes("Trainers"))
    "Wear high heels".fail
    else
    p.success

    //Third CHECK
    def checkSobriety(p : Person): Validation[String, Person]
    = if (Set(Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious) contains p.sobriety)
    "Sober Up!".fail
    else
    def checkSobriety(p : Person): Validation[String, Person]
    = if (Set(Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious) contains p.sobriety)
    "Sober Up!".fail
    else
    p.success
    }

    @@ -52,7 +52,7 @@ trait Nightclub {
    * Now let's compose some validation checks
    *
    */
    object ClubbedToDeath extends Nightclub {
    object ClubbedToDeath extends Nightclub {
    def costToEnter(p : Person) : Validation[String, Double] = {

    //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR
    @@ -65,7 +65,7 @@ object ClubbedToDeath extends Nightclub {
    }

    // Now let's see these in action

    object Test1 {
    val Ken = Person(Gender.Male, 28, Set("Tie", "Shirt"), Sobriety.Tipsy)

    val Dave = Person(Gender.Male, 41, Set("Tie", "Jeans"), Sobriety.Sober)
    @@ -84,32 +84,33 @@ object ClubbedToDeath extends Nightclub {

    ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: scalaz.Validation[String,Double] = Failure(Sober Up!)

    }
    /**
    * The thing to note here is how the Validations can be composed together in a for-comprehension.
    * The thing to note here is how the Validations can be composed together in a for-comprehension.
    * Scala's type system is making sure that failures flow through your computation in a safe manner.
    */


    /**
    /**
    * Part Two : Club Tropicana
    *
    * Part One showed monadic composition, which from the perspective of Validation is *fail-fast*.
    * That is, any failed check shortcircuits subsequent checks. This nicely models nightclubs in the
    * real world, as anyone who has dashed home for a pair of smart shoes and returned, only to be
    * told that your tie does not pass muster, will attest.
    * Part One showed monadic composition, which from the perspective of Validation is *fail-fast*.
    * That is, any failed check shortcircuits subsequent checks. This nicely models nightclubs in the
    * real world, as anyone who has dashed home for a pair of smart shoes and returned, only to be
    * told that your tie does not pass muster, will attest.
    *
    * But what about an ideal nightclub? One that tells you *everything* that is wrong with you.
    *
    * Applicative functors to the rescue!
    * Applicative functors to the rescue!
    *
    */

    object ClubTropicana extends Nightclub {
    object ClubTropicana extends Nightclub {
    def costToEnter(p : Person) : ValidationNEL[String, Double] = {

    //PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (a NonEmptyList, or NEL)
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) {
    case (_, _, c) => if (c.gender == Gender.Female) 0D else 7.5D
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) {
    case (_, _, c) => if (c.gender == Gender.Female) 0D else 7.5D
    }
    }
    }
    @@ -119,15 +120,17 @@ object ClubTropicana extends Nightclub {
    * And the use? Dave tried the second nightclub after a few more drinks in the pub
    *
    */

    object Test2 {
    import Test1._
    ClubTropicana costToEnter (Dave.copy(sobriety = Sobriety.Paralytic)) //res6: scalaz.Scalaz.ValidationNEL[String,Double] = Failure(NonEmptyList(Too Old!, Sober Up!))

    ClubTropicana costToEnter(Ruby) //res7: scalaz.Scalaz.ValidationNEL[String,Double] = Success(0.0)

    }
    /**
    *
    * So, what have we done? Well, with a *tiny change* (and no changes to the individual checks themselves),
    * we have completely changed the behaviour to accumulate all errors, rather than halting at the first sign
    * So, what have we done? Well, with a *tiny change* (and no changes to the individual checks themselves),
    * we have completely changed the behaviour to accumulate all errors, rather than halting at the first sign
    * of trouble. Imagine trying to do this in Java, using exceptions, with ten checks.
    *
    */
    @@ -143,25 +146,24 @@ object ClubTropicana extends Nightclub {
    *
    */
    object GayBar extends Nightclub {
    def checkGender(p : Person) : Validation[String, Person] =

    def checkGender(p : Person) : Validation[String, Person] =
    if (p.gender != Gender.Male)
    "Men Only".fail
    else
    p.success

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks map (_(p).liftFailNel)).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs =>
    val checks : List[Person => Validation[String, Person]] = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    (checks map {(_ : (Person => Validation[String, Person])).apply(p).liftFailNel}).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    case c :: _ => c.age + 1.5D
    }
    }
    }
    }

    /**
    * As always; the point is that our validation functions are "static";
    * As always; the point is that our validation functions are "static";
    * we do not need to change the way they have been coded because we want to combine them in different ways
    */

  8. @oxbowlakes oxbowlakes revised this gist Jul 5, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -52,7 +52,7 @@ trait Nightclub {
    * Now let's compose some validation checks
    *
    */
    object ClubbedToDeath extends Nighclub {
    object ClubbedToDeath extends Nightclub {
    def costToEnter(p : Person) : Validation[String, Double] = {

    //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR
  9. @oxbowlakes oxbowlakes revised this gist May 16, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@
    *
    * (In which we will see how to let the type system help you handle failure)...
    *
    * First let's define a domain
    * First let's define a domain. (All the following requires scala 2.8.x and scalaz 5.0)
    */

    import scalaz._
  10. @oxbowlakes oxbowlakes revised this gist May 14, 2011. 1 changed file with 57 additions and 51 deletions.
    108 changes: 57 additions & 51 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -19,30 +19,31 @@ case class Person(gender : Gender.Value, age : Int, clothes : Set[String], sobri
    * Let's define a trait which will contain the checks that *all* nightclubs make!
    */
    trait Nightclub {
    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    = if (p.age < 18)
    "Too Young!".fail
    else if (p.age > 40)
    "Too Old!".fail
    else

    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    = if (p.age < 18)
    "Too Young!".fail
    else if (p.age > 40)
    "Too Old!".fail
    else
    p.success

    //Second CHECK
    def checkClothes(p : Person) : Validation[String, Person]
    = if (p.gender == Gender.Male && !p.clothes("Tie"))
    "Smarten Up!".fail
    else if (p.gender == Gender.Female && p.clothes("Trainers"))
    "Wear high heels".fail
    else
    p.success

    //Third CHECK
    def checkSobriety(p : Person): Validation[String, Person]
    = if (Set(Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious) contains p.sobriety)
    "Sober Up!".fail
    else
    p.success
    //Second CHECK
    def checkClothes(p : Person) : Validation[String, Person]
    = if (p.gender == Gender.Male && !p.clothes("Tie"))
    "Smarten Up!".fail
    else if (p.gender == Gender.Female && p.clothes("Trainers"))
    "Wear high heels".fail
    else
    p.success

    //Third CHECK
    def checkSobriety(p : Person): Validation[String, Person]
    = if (Set(Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious) contains p.sobriety)
    "Sober Up!".fail
    else
    p.success
    }

    /**
    @@ -52,36 +53,36 @@ trait Nightclub {
    *
    */
    object ClubbedToDeath extends Nighclub {
    def costToEnter(p : Person) : Validation[String, Double] = {

    //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR
    for {
    a <- checkAge(p)
    b <- checkClothes(a)
    c <- checkSobriety(b)
    } yield (if (c.gender == Gender.Female) 0D else 5D)
    }
    }
    def costToEnter(p : Person) : Validation[String, Double] = {

    //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR
    for {
    a <- checkAge(p)
    b <- checkClothes(a)
    c <- checkSobriety(b)
    } yield (if (c.gender == Gender.Female) 0D else 5D)
    }
    }

    // Now let's see these in action

    val Ken = Person(Gender.Male, 28, Set("Tie", "Shirt"), Sobriety.Tipsy)
    val Ken = Person(Gender.Male, 28, Set("Tie", "Shirt"), Sobriety.Tipsy)

    val Dave = Person(Gender.Male, 41, Set("Tie", "Jeans"), Sobriety.Sober)
    val Dave = Person(Gender.Male, 41, Set("Tie", "Jeans"), Sobriety.Sober)

    val Ruby = Person(Gender.Female, 25, Set("High Heels"), Sobriety.Tipsy)
    val Ruby = Person(Gender.Female, 25, Set("High Heels"), Sobriety.Tipsy)

    // Let's go clubbing!

    ClubbedToDeath costToEnter Dave //res0: scalaz.Validation[String,Double] = Failure(Too Old!)
    ClubbedToDeath costToEnter Dave //res0: scalaz.Validation[String,Double] = Failure(Too Old!)

    ClubbedToDeath costToEnter Ken //res1: scalaz.Validation[String,Double] = Success(5.0)
    ClubbedToDeath costToEnter Ken //res1: scalaz.Validation[String,Double] = Success(5.0)

    ClubbedToDeath costToEnter Ruby //res2: scalaz.Validation[String,Double] = Success(0.0)
    ClubbedToDeath costToEnter Ruby //res2: scalaz.Validation[String,Double] = Success(0.0)

    ClubbedToDeath costToEnter (Ruby.copy(age = 17)) //res3: scalaz.Validation[String,Double] = Failure(Too Young!)
    ClubbedToDeath costToEnter (Ruby.copy(age = 17)) //res3: scalaz.Validation[String,Double] = Failure(Too Young!)

    ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: scalaz.Validation[String,Double] = Failure(Sober Up!)
    ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: scalaz.Validation[String,Double] = Failure(Sober Up!)

    /**
    * The thing to note here is how the Validations can be composed together in a for-comprehension.
    @@ -104,22 +105,24 @@ ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: s
    */

    object ClubTropicana extends Nightclub {
    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    def costToEnter(p : Person) : ValidationNEL[String, Double] = {

    //PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (a NonEmptyList, or NEL)
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) { case (_, _, c) => if (c.gender == Gender.Female) 0D else 7.5D }
    }
    }
    //PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (a NonEmptyList, or NEL)
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) {
    case (_, _, c) => if (c.gender == Gender.Female) 0D else 7.5D
    }
    }
    }

    /**
    *
    * And the use? Dave tried the second nightclub after a few more drinks in the pub
    *
    */

    ClubTropicana costToEnter (Dave.copy(sobriety = Sobriety.Paralytic)) //res6: scalaz.Scalaz.ValidationNEL[String,Double] = Failure(NonEmptyList(Too Old!, Sober Up!))
    ClubTropicana costToEnter (Dave.copy(sobriety = Sobriety.Paralytic)) //res6: scalaz.Scalaz.ValidationNEL[String,Double] = Failure(NonEmptyList(Too Old!, Sober Up!))

    ClubTropicana costToEnter(Ruby) //res7: scalaz.Scalaz.ValidationNEL[String,Double] = Success(0.0)
    ClubTropicana costToEnter(Ruby) //res7: scalaz.Scalaz.ValidationNEL[String,Double] = Success(0.0)

    /**
    *
    @@ -149,10 +152,13 @@ object GayBar extends Nightclub {

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks map (_(p).liftFailNel)).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs => (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D }

    }}
    checks map (_(p).liftFailNel)).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs =>
    (xs : @unchecked) match {
    case c :: _ => c.age + 1.5D
    }
    }
    }
    }

    /**
    * As always; the point is that our validation functions are "static";
  11. @oxbowlakes oxbowlakes revised this gist May 13, 2011. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,9 @@ object Gender extends Enumeration { val Male, Female = Value }

    case class Person(gender : Gender.Value, age : Int, clothes : Set[String], sobriety : Sobriety.Value)

    /**
    * Let's define a trait which will contain the checks that *all* nightclubs make!
    */
    trait Nightclub {
    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    @@ -104,7 +107,7 @@ object ClubTropicana extends Nightclub {
    def costToEnter(p : Person) : ValidationNEL[String, Double] = {

    //PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (a NonEmptyList, or NEL)
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) { case (_, _, c) => if (c.gender == Gender.Female) 0D else 5D }
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) { case (_, _, c) => if (c.gender == Gender.Female) 0D else 7.5D }
    }
    }

    @@ -137,11 +140,17 @@ ClubTropicana costToEnter(Ruby) //res7: scalaz.Scalaz.ValidationNEL[String,Doubl
    *
    */
    object GayBar extends Nightclub {

    def checkGender(p : Person) : Validation[String, Person] =
    if (p.gender != Gender.Male)
    "Men Only".fail
    else
    p.success

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _)
    val checks = List(checkAge _, checkClothes _, checkSobriety _, checkGender _)
    checks map (_(p).liftFailNel)).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs => (xs : @unchecked) match {
    case c :: _ => if (c.gender == Gender.Female) 0D else 5D }
    case c :: _ => c.age + 1.5D }

    }}

  12. @oxbowlakes oxbowlakes revised this gist May 13, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -96,7 +96,7 @@ ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: s
    *
    * But what about an ideal nightclub? One that tells you *everything* that is wrong with you.
    *
    * Applicative functors to the rescue! I have highlighted the changes from the previous snippet in blue
    * Applicative functors to the rescue!
    *
    */

  13. @oxbowlakes oxbowlakes created this gist May 13, 2011.
    152 changes: 152 additions & 0 deletions 3nightclubs.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    /**
    * Part Zero : 10:15 Saturday Night
    *
    * (In which we will see how to let the type system help you handle failure)...
    *
    * First let's define a domain
    */

    import scalaz._
    import Scalaz._

    object Sobriety extends Enumeration { val Sober, Tipsy, Drunk, Paralytic, Unconscious = Value }

    object Gender extends Enumeration { val Male, Female = Value }

    case class Person(gender : Gender.Value, age : Int, clothes : Set[String], sobriety : Sobriety.Value)

    trait Nightclub {
    //First CHECK
    def checkAge(p : Person) : Validation[String, Person]
    = if (p.age < 18)
    "Too Young!".fail
    else if (p.age > 40)
    "Too Old!".fail
    else
    p.success

    //Second CHECK
    def checkClothes(p : Person) : Validation[String, Person]
    = if (p.gender == Gender.Male && !p.clothes("Tie"))
    "Smarten Up!".fail
    else if (p.gender == Gender.Female && p.clothes("Trainers"))
    "Wear high heels".fail
    else
    p.success

    //Third CHECK
    def checkSobriety(p : Person): Validation[String, Person]
    = if (Set(Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious) contains p.sobriety)
    "Sober Up!".fail
    else
    p.success
    }

    /**
    * Part One : Clubbed to Death
    *
    * Now let's compose some validation checks
    *
    */
    object ClubbedToDeath extends Nighclub {
    def costToEnter(p : Person) : Validation[String, Double] = {

    //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR
    for {
    a <- checkAge(p)
    b <- checkClothes(a)
    c <- checkSobriety(b)
    } yield (if (c.gender == Gender.Female) 0D else 5D)
    }
    }

    // Now let's see these in action

    val Ken = Person(Gender.Male, 28, Set("Tie", "Shirt"), Sobriety.Tipsy)

    val Dave = Person(Gender.Male, 41, Set("Tie", "Jeans"), Sobriety.Sober)

    val Ruby = Person(Gender.Female, 25, Set("High Heels"), Sobriety.Tipsy)

    // Let's go clubbing!

    ClubbedToDeath costToEnter Dave //res0: scalaz.Validation[String,Double] = Failure(Too Old!)

    ClubbedToDeath costToEnter Ken //res1: scalaz.Validation[String,Double] = Success(5.0)

    ClubbedToDeath costToEnter Ruby //res2: scalaz.Validation[String,Double] = Success(0.0)

    ClubbedToDeath costToEnter (Ruby.copy(age = 17)) //res3: scalaz.Validation[String,Double] = Failure(Too Young!)

    ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: scalaz.Validation[String,Double] = Failure(Sober Up!)

    /**
    * The thing to note here is how the Validations can be composed together in a for-comprehension.
    * Scala's type system is making sure that failures flow through your computation in a safe manner.
    */


    /**
    * Part Two : Club Tropicana
    *
    * Part One showed monadic composition, which from the perspective of Validation is *fail-fast*.
    * That is, any failed check shortcircuits subsequent checks. This nicely models nightclubs in the
    * real world, as anyone who has dashed home for a pair of smart shoes and returned, only to be
    * told that your tie does not pass muster, will attest.
    *
    * But what about an ideal nightclub? One that tells you *everything* that is wrong with you.
    *
    * Applicative functors to the rescue! I have highlighted the changes from the previous snippet in blue
    *
    */

    object ClubTropicana extends Nightclub {
    def costToEnter(p : Person) : ValidationNEL[String, Double] = {

    //PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (a NonEmptyList, or NEL)
    (checkAge(p).liftFailNel |@| checkClothes(p).liftFailNel |@| checkSobriety(p).liftFailNel) { case (_, _, c) => if (c.gender == Gender.Female) 0D else 5D }
    }
    }

    /**
    *
    * And the use? Dave tried the second nightclub after a few more drinks in the pub
    *
    */

    ClubTropicana costToEnter (Dave.copy(sobriety = Sobriety.Paralytic)) //res6: scalaz.Scalaz.ValidationNEL[String,Double] = Failure(NonEmptyList(Too Old!, Sober Up!))

    ClubTropicana costToEnter(Ruby) //res7: scalaz.Scalaz.ValidationNEL[String,Double] = Success(0.0)

    /**
    *
    * So, what have we done? Well, with a *tiny change* (and no changes to the individual checks themselves),
    * we have completely changed the behaviour to accumulate all errors, rather than halting at the first sign
    * of trouble. Imagine trying to do this in Java, using exceptions, with ten checks.
    *
    */

    /**
    *
    * Part Three : Gay Bar
    *
    * And for those wondering how to do this with a *very long list* of checks. Use sequence:
    * List[ValidationNEL[E, A]] ~> (via sequence) ~> ValidationNEL[E, List[A]]
    *
    * Here we go (unfortunately we need to use a type lambda on the call to sequence):
    *
    */
    object GayBar extends Nightclub {

    def costToEnter(p : Person) : ValidationNEL[String, Double] = {
    val checks = List(checkAge _, checkClothes _, checkSobriety _)
    checks map (_(p).liftFailNel)).sequence[({type l[a]=ValidationNEL[String, a]})#l, Person] map { xs => (xs : @unchecked) match {
    case c :: _ => if (c.gender == Gender.Female) 0D else 5D }

    }}

    /**
    * As always; the point is that our validation functions are "static";
    * we do not need to change the way they have been coded because we want to combine them in different ways
    */