Last active
August 29, 2015 13:56
-
-
Save swlaschin/9008814 to your computer and use it in GitHub Desktop.
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 characters
// ============================== | |
// 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