Created
April 29, 2012 22:07
-
-
Save ms-ati/2553490 to your computer and use it in GitHub Desktop.
Revisions
-
ms-ati revised this gist
Apr 30, 2012 . 1 changed file with 4 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 @@ -20,6 +20,10 @@ require 'pp' require 'rumonade' Sobriety = [:sober, :tipsy, :drunk, :paralytic, :unconscious] Gender = [:male, :female] Person = Struct.new(:gender, :age, :clothes, :sobriety) do # accept a block to modify the copy before returning it def copy -
ms-ati revised this gist
Apr 30, 2012 . 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 @@ ## ## Based on Scala version here: https://gist.github.com/970717 ## ## Demonstrates applicative validation in Ruby, inspired by the blog post: ## "An example of applicative validation in FSharpx" ## (http://bugsquash.blogspot.com/2012/03/example-of-applicative-validation-in.html) #### -
ms-ati revised this gist
Apr 30, 2012 . 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 @@ -1,7 +1,7 @@ #### ## Ruby port of "A Tale of 3 Nightclubs" ## ## Based on Scala version here: https://gist.github.com/970717 ## ## Demonstrates applicative validation in Ruby, inspired the blog post ## "An example of applicative validation in FSharpx" -
ms-ati revised this gist
Apr 29, 2012 . 1 changed file with 3 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 @@ -3,9 +3,9 @@ ## ## Based on scala version here: https://gist.github.com/970717 ## ## Demonstrates applicative validation in Ruby, inspired the blog post ## "An example of applicative validation in FSharpx" ## (http://bugsquash.blogspot.com/2012/03/example-of-applicative-validation-in.html) #### # -
ms-ati revised this gist
Apr 29, 2012 . 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 @@ -1,5 +1,5 @@ #### ## Ruby port of "A Tale of 3 Nightclubs" ## ## Based on scala version here: https://gist.github.com/970717 ## -
ms-ati revised this gist
Apr 29, 2012 . 1 changed file with 10 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 @@ -1,3 +1,13 @@ #### ## Ruby port of "A Tale of 3 Nightclubs", using rumonade gem, ## ## Based on scala version here: https://gist.github.com/970717 ## ## Demonstrates applicative validation in Ruby, inspired the blog post: ## An example of applicative validation in FSharpx ## http://bugsquash.blogspot.com/2012/03/example-of-applicative-validation-in.html #### # # Part Zero : 10:15 Saturday Night # -
ms-ati revised this gist
Apr 29, 2012 . 1 changed file with 1 addition 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 @@ -11,6 +11,7 @@ require 'rumonade' Person = Struct.new(:gender, :age, :clothes, :sobriety) do # accept a block to modify the copy before returning it def copy a_copy = dup yield a_copy if block_given? -
ms-ati created this gist
Apr 29, 2012 .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,179 @@ # # 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 ruby 1.9 or compatible, and rumonade >= 0.3.0) # require 'pp' require 'rumonade' Person = Struct.new(:gender, :age, :clothes, :sobriety) do def copy a_copy = dup yield a_copy if block_given? a_copy end end # # Let's define a trait which will contain the checks that *all* nightclubs make! # module Nightclub # First CHECK age def check_age(p) if p.age < 18 Left("Too Young!") elsif p.age > 40 Left("Too Old!") else Right(p) end end # Second CHECK clothes def check_clothes(p) if p.gender == :male && !p.clothes.include?("Tie") Left("Smarten Up!") elsif p.gender == :female && p.clothes.include?("Trainers") Left("Wear high heels") else Right(p) end end # Third CHECK sobriety def check_sobriety(p) if [:drunk, :paralytic, :unconscious].include?(p.sobriety) Left("Sober Up!") else Right(p) end end end # # Part One : Clubbed to Death # # Now let's compose some validation checks # class ClubbedToDeath include Nightclub def cost_to_enter(p) # PERFORM THE CHECKS USING Monadic flat_map operation (need to add for-expression sugar!) check_age(p).right.flat_map do |a| check_clothes(a).right.flat_map do |b| check_sobriety(b).right.flat_map do |c| Right(if c.gender == :female then 0 else 5 end) end end end end end # Now let's see these in action module Test1 Ken = Person.new(:male, 28, ["Tie", "Shirt"], :tipsy) Dave = Person.new(:male, 41, ["Tie", "Jeans"], :sober) Ruby = Person.new(:female, 25, ["High Heels"], :tipsy) # Let's go clubbing! COSTS = [ ClubbedToDeath.new.cost_to_enter(Dave), # Left("Too Old!") ClubbedToDeath.new.cost_to_enter(Ken), # Right(5) ClubbedToDeath.new.cost_to_enter(Ruby), # Right(0) ClubbedToDeath.new.cost_to_enter(Ruby.copy { |r| r.age = 17 }), # Left("Too Young!") ClubbedToDeath.new.cost_to_enter(Ken.copy { |r| r.sobriety = :unconscious }) # Left("Sober Up!") ] end puts "Part One: Clubbed to Death" pp Test1::COSTS # # Part Two : Club Tropicana # # Part One showed monadic composition, which from the perspective of Validation is *fail-fast*. # That is, any failed check short circuits 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! # class ClubTropicana include Nightclub def cost_to_enter(p) # PERFORM THE CHECKS USING applicative functors, accumulating failure via a monoid (an Array in this case) (check_age(p).lift_to_a + check_clothes(p).lift_to_a + check_sobriety(p).lift_to_a).right.map do |a,b,c| if c.gender == :female then 0 else 7.5 end end end end # # And the use? Dave tried the second nightclub after a few more drinks in the pub # module Test2 include Test1 COSTS = [ ClubTropicana.new.cost_to_enter(Dave.copy { |d| d.sobriety = :paralytic }), # Left(["Too Old!", "Sober Up!"]) ClubTropicana.new.cost_to_enter(Ruby) # Right(0) ] end puts "\nPart Two: Club Tropicana" pp Test2::COSTS # # 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 inject: # list_of_eithers.inject(:+) # class GayBar include Nightclub def check_gender(p) if p.gender != :male Left("Men Only") else Right(p) end end def cost_to_enter(p) checks = [method(:check_age), method(:check_clothes), method(:check_sobriety), method(:check_gender)] checks.map { |chk| chk.call(p).lift_to_a }.inject(:+).right.map { |c| c.last.age + 1.5 } end end module Test3 include Test2 COSTS = [ GayBar.new.cost_to_enter(Person.new(:male, 59, ["Jeans"], :paralytic)), # Left(["Too Old!", "Smarten Up!", "Sober Up!"]) GayBar.new.cost_to_enter(Ruby.copy { |r| r.clothes = ["Trainers"] }), # Left(["Wear high heels", "Men Only"]) GayBar.new.cost_to_enter(Ken) # Right(29.5) ] end puts "\nPart Three: Gay Bar" pp Test3::COSTS # # 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 #