Thanks to type-inference and partial application in F#
data-driven programming is succinct and readable.
Let's imagine we are selling car insurances. Before we sell it to a customer we try to determine if the customer is a potential customer for us by checking sex and age.
A simple customer model:
type Sex =
| Male
| Female
type Customer =
{
Name : string
Born : System.DateTime
Sex : Sex
}
Next we like to define an exclusion list (table) where if a customer matches any row in the exclusion list the customer is denied our car insurance.
// If a row matches the Customer isn't eligble for the car insurance
let exclusionList =
let __ _ = true
let olderThan x y = x < y
let youngerThan x y = x > y
[|
// Sex Age Reason for denial
__ , olderThan 65 , "Not allowed for senior citizens"
__ , youngerThan 16 , "Not allowed for children"
(=) Male , youngerThan 25 , "Not allowed for young males"
|]
Because of type-inference and partial application the exclusion list is flexible yet easy to understand.
Finally, we define a function that using the exclusion list (a table) split the customers into two buckets; potential and denied customers.
// 'splitChoice' is a combination of Array.partition and Array.choose
let splitChoice (c : 'T -> Choice<'U, 'V>) (ts : 'T []) : 'U []*'V [] =
let us = ResizeArray ts.Length
let vs = ResizeArray ts.Length
for t in ts do
match c t with
| Choice1Of2 u -> us.Add u
| Choice2Of2 v -> vs.Add v
us.ToArray (), vs.ToArray ()
// Splits customers into two buckets; potential customers and denied custmers.
// The denied customer bucket also includes the reason for denying the car insurance
let splitCustomers (today : System.DateTime) (cs : Customer []) : Customer []*(string*Customer) [] =
let choose (c : Customer) =
let age = today.Year - c.Born.Year
let sex = c.Sex
match exclusionList |> Array.tryFind (fun (_, testAge, testSex) -> testAge age && testSex sex) with
| None -> Choice1Of2 c
| Some (description, _, _) -> Choice2Of2 (description, c)
cs |> splitChoice choose
To wrap up let's define some customers and see if they are any potential customers for our car insurance:
let customers =
let c n s y m d: Customer = { Name = n; Born = System.DateTime (y, m, d); Sex = s }
[|
// Name Sex Born
c "Clint Eastwood Jr." Male 1930 05 31
c "Bill Gates" Male 1955 10 28
c "Melina Gates" Female 1964 08 15
c "Justin Drew Bieber" Male 1994 03 01
c "Sophie Turner" Female 1996 02 21
c "Isaac Hempstead Wright" Male 1999 04 09
|]
[<EntryPoint>]
let main argv =
let potential, denied = splitCustomers (System.DateTime (2014, 06, 01)) customers
printfn "Potential Customers (%d)\n%A" potential.Length potential
printfn "Denied Customers (%d)\n%A" denied.Length denied
0
This prints:
Potential Customers (3)
[|{Name = "Bill Gates";
Born = 1955-10-28 00:00:00;
Sex = Male;}; {Name = "Melina Gates";
Born = 1964-08-15 00:00:00;
Sex = Female;}; {Name = "Sophie Turner";
Born = 1996-02-21 00:00:00;
Sex = Female;}|]
Denied Customers (3)
[|("Not allowed for senior citizens", {Name = "Clint Eastwood Jr.";
Born = 1930-05-31 00:00:00;
Sex = Male;});
("Not allowed for young males", {Name = "Justin Drew Bieber";
Born = 1994-03-01 00:00:00;
Sex = Male;});
("Not allowed for children", {Name = "Isaac Hempstead Wright";
Born = 1999-04-09 00:00:00;
Sex = Male;})|]