Skip to content

Instantly share code, notes, and snippets.

@swlaschin
Last active August 29, 2015 13:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save swlaschin/9008814 to your computer and use it in GitHub Desktop.
Save swlaschin/9008814 to your computer and use it in GitHub Desktop.
// ==============================
// This gist is in response to
// http://programmers.stackexchange.com/questions/228939/how-to-improve-upon-blochs-builder-pattern-to-make-it-more-appropriate-for-use
//
// This is a simple bit of code to do the same thing using the Either monad (e.g. success/failure) and applicatives
// This code is (a) more generic (b) much shorter (c) more type safe
//
// Compare with the Java code at https://gist.github.com/swlaschin/9009343
// ==============================
// ==================================
// Generic Either with applicatives
//
// Works with any domain and would be in a library
// ==================================
type Result<'T> =
| Success of 'T
| Failure of string
// apply a function
let (<*>) f x =
match (f,x) with
| Success f, Success x -> Success (f x)
| Failure e, _ | _, Failure e -> Failure e
// lift a function
let (<^>) f x =
match x with
| Success x -> Success (f x)
| Failure e -> Failure e
// ==================================
// Domain specific types
// Each type has a matching constructor that returns Success/Failure
//
// These would be used in many different places in the domain.
// E.g. the same "Age" "Color" type could be used as part of other types
// not just as part of UserConfig
// ==================================
open System.Text.RegularExpressions
type Name = Name of string
type Age = Age of int
type Color = Red|Blue|Green|HotPink
let name = function
| null ->
Failure "name must not be null"
| s when Regex.IsMatch(s,@"\w+") ->
// on Success wrap string in Name type
Success (Name s)
| s ->
Failure (sprintf "Name %s may not be empty, and must contain only letters digits and underscores" s)
let age = function
| i when i < 0 ->
Failure (sprintf "Age %i is less than zero" i)
| i ->
// on Success wrap int in Age class
Success (Age i)
let color = function
| "red" -> Success Red
| "green" -> Success Green
| "blue" -> Success Blue
| "hotpink" -> Success HotPink
| s ->
Failure (sprintf "Color %s is not red, blue, green, or hot pink." s)
// ==================================
// Finally, the specific domain type that needs to be built with error handling
// ==================================
type UserConfig = {
sName: Name
iAge: Age
sFavColor: Color }
// a constructor function
let userConfig name age color =
{sName = name; iAge = age; sFavColor = color }
// ==================================
// Some tests
// ==================================
// test the happy path
let uc1 = userConfig <^> name "Kermit" <*> age 50 <*> color "green"
// Result<UserConfig> = Success {sName = Name "Kermit";iAge = Age 50; sFavColor = Green;}
// test bad name
let uc2 = userConfig <^> name "" <*> age 50 <*> color "green"
// Result<UserConfig> = Failure "Name may not be empty, and must contain only letters digits and underscores"
// test bad age
let uc3 = userConfig <^> name "Kermit" <*> age -50 <*> color "green"
// Result<UserConfig> = Failure "Age -50 is less than zero"
// test bad color
let uc4 = userConfig <^> name "Kermit" <*> age 50 <*> color "yellow"
// Result<UserConfig> = Failure "Color yellow is not red, blue, green, or hot pink."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment