Revisions
-
oxbowlakes revised this gist
Jul 11, 2011 . 1 changed file with 9 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal 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 -
oxbowlakes revised this gist
Jul 11, 2011 . 1 changed file with 3 additions and 9 deletions.There are no files selected for viewing
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 charactersOriginal 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 { 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 { case c :: _ => c.age + 1.5D } } } -
oxbowlakes revised this gist
Jul 11, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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 (_.liftFailNel) apply p) map { xs => (xs : @unchecked) match { case c :: _ => c.age + 1.5D } -
oxbowlakes revised this gist
Jul 11, 2011 . 1 changed file with 12 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal 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 } } } } /** -
oxbowlakes revised this gist
Jul 9, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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.9.x and scalaz 6.0) */ import scalaz._ -
oxbowlakes revised this gist
Jul 8, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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(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 -
oxbowlakes revised this gist
Jul 8, 2011 . 1 changed file with 44 additions and 42 deletions.There are no files selected for viewing
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 charactersOriginal 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 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 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,7 +52,7 @@ trait Nightclub { * Now let's compose some validation checks * */ 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. * 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! * */ 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 } } } @@ -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 * 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] = if (p.gender != Gender.Male) "Men Only".fail else p.success def costToEnter(p : Person) : ValidationNEL[String, Double] = { 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 } } } } /** * 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 */ -
oxbowlakes revised this gist
Jul 5, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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 Nightclub { def costToEnter(p : Person) : Validation[String, Double] = { //PERFORM THE CHECKS USING Monadic "for comprehension" SUGAR -
oxbowlakes revised this gist
May 16, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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) */ import scalaz._ -
oxbowlakes revised this gist
May 14, 2011 . 1 changed file with 57 additions and 51 deletions.There are no files selected for viewing
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 charactersOriginal 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 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) } } // 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. @@ -104,22 +105,24 @@ ClubbedToDeath costToEnter (Ken.copy(sobriety = Sobriety.Unconscious)) //res5: s */ 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 } } } /** * * 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) /** * @@ -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 } } } } /** * As always; the point is that our validation functions are "static"; -
oxbowlakes revised this gist
May 13, 2011 . 1 changed file with 12 additions and 3 deletions.There are no files selected for viewing
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 charactersOriginal 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 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 _, 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 } }} -
oxbowlakes revised this gist
May 13, 2011 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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! * */ -
oxbowlakes created this gist
May 13, 2011 .There are no files selected for viewing
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 charactersOriginal 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 */