A Tale of 3 Nightclubs
This F# tutorial is based on a Scalaz tutorial by Chris Marshall and was originally ported to fsharpx by Mauricio Scheffer.
Additional resources:
- Railway Oriented Programming by Scott Wlaschin - A functional approach to error handling
Part Zero : 10:15 Saturday Night
We start by referencing Chessie and opening the ErrorHandling module and define a simple domain for nightclubs:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
|
Now we define some validation methods that all nightclubs will perform:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
|
Part One : Clubbed to Death
Now let's compose some validation checks via syntactic sugar:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
|
Let's see how the validation works in action:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
|
The thing to note here is how the Validations can be composed together in a computation expression. The 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 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!
Let's compose some validation checks that accumulate failures :
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
|
The usage is the same as above except that as a result we will get either a success or a list of accumulated error messages from all the checks.
Dave tried the second nightclub after a few more drinks in the pub:
1: 2: 3: 4: 5: |
|
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 using exceptions, with ten checks.
Part Three : Gay bar
And for those wondering how to do this with a very long list of checks here is a solution:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
|
The usage is the same as above:
1: 2: 3: 4: 5: 6: |
|
| Sober
| Tipsy
| Drunk
| Paralytic
| Unconscious
Full name: A-tale-of-3-nightclubs-fsharp.Sobriety
| Male
| Female
Full name: A-tale-of-3-nightclubs-fsharp.Gender
{Gender: Gender;
Age: int;
Clothes: Set<string>;
Sobriety: Sobriety;}
Full name: A-tale-of-3-nightclubs-fsharp.Person
Person.Gender: Gender
--------------------
type Gender =
| Male
| Female
Full name: A-tale-of-3-nightclubs-fsharp.Gender
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
module Set
from Microsoft.FSharp.Collections
--------------------
type Set<'T (requires comparison)> =
interface IComparable
interface IEnumerable
interface IEnumerable<'T>
interface ICollection<'T>
new : elements:seq<'T> -> Set<'T>
member Add : value:'T -> Set<'T>
member Contains : value:'T -> bool
override Equals : obj -> bool
member IsProperSubsetOf : otherSet:Set<'T> -> bool
member IsProperSupersetOf : otherSet:Set<'T> -> bool
...
Full name: Microsoft.FSharp.Collections.Set<_>
--------------------
new : elements:seq<'T> -> Set<'T>
Person.Sobriety: Sobriety
--------------------
type Sobriety =
| Sober
| Tipsy
| Drunk
| Paralytic
| Unconscious
Full name: A-tale-of-3-nightclubs-fsharp.Sobriety
Full name: A-tale-of-3-nightclubs-fsharp.Club.checkAge
Full name: Chessie.ErrorHandling.Trial.fail
Full name: Chessie.ErrorHandling.Trial.ok
Full name: A-tale-of-3-nightclubs-fsharp.Club.checkClothes
Full name: Microsoft.FSharp.Core.Operators.not
Full name: A-tale-of-3-nightclubs-fsharp.Club.checkSobriety
from A-tale-of-3-nightclubs-fsharp
Full name: A-tale-of-3-nightclubs-fsharp.ClubbedToDeath.costToEnter
Full name: Chessie.ErrorHandling.Trial.trial
Full name: A-tale-of-3-nightclubs-fsharp.Ken
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
Full name: A-tale-of-3-nightclubs-fsharp.Dave
Full name: A-tale-of-3-nightclubs-fsharp.Ruby
from A-tale-of-3-nightclubs-fsharp
Full name: A-tale-of-3-nightclubs-fsharp.ClubTropicana.costToEnter
Full name: Chessie.ErrorHandling.Trial.collect
Full name: A-tale-of-3-nightclubs-fsharp.daveParalytic
from A-tale-of-3-nightclubs-fsharp
Full name: A-tale-of-3-nightclubs-fsharp.GayBar.checkGender
Full name: A-tale-of-3-nightclubs-fsharp.GayBar.costToEnter
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.map
Full name: A-tale-of-3-nightclubs-fsharp.person
from A-tale-of-3-nightclubs-fsharp