Business Logic and F#
May 26, 2015 2 Comments
One of the reasons I like F# so much is that it allows me to think about the problem I am trying to solve, not about the language syntax and coding around language constructs. Consider this example.
I am putting on an art show in my neighborhood and I managed to obtain 3 paintings of cultural significance:
Starry Night
Sunday Afternoon on the Island of La Grande Jatte
Dogs Playing Poker
Each painting is in its own room and due to the volume of people that the art gallery can support, a person can only visit 1 painting. 1,000 tickets sold and all 1,000 people are going to show up. This is a hot event.
I needed a way to forecast how many people will go into each room. Since all 3 paintings are immensely popular, I could assume that each room will have 1/3 the number of visitors. However, I wanted to be be a bit more precise and I know that each painting has a certain number of tags associated with them:
Tag | Starry Night | Afternoon | Poker |
Impressionism | X | X | |
Nature | X | X | |
Leisure Activity | X | X | |
Modernism | X |
Assuming that people will want to go see paintings with tags that interest them, paintings that have tag overlap will split visitors, paintings with no tag overlap will see more visitors, and paintings with more tags will draw more visitors. In Excel:
Tag | Starry Night | Afternoon | Poker | Total |
Impressionism | 1 | 1 | 2 | |
Nature | 1 | 1 | 2 | |
Leisure Activity | 1 | 1 | 2 | |
Modernism | 1 | 1 | ||
Tag | Starry Night | Afternoon | Poker | |
Impressionism | 0.5 | 0.5 | ||
Nature | 0.5 | 0.5 | ||
Leisure Activity | 0.5 | 0.5 | ||
Modernism | 1 | |||
Tag | People | Starry Night | Afternoon | Poker |
Impressionism | 250 | 125 | 125 | |
Nature | 250 | 125 | 125 | |
Leisure Activity | 250 | 125 | 125 | |
Modernism | 250 | 250 | ||
1,000 | 250 | 375 | 375 |
Putting this to code, I opened up the F# REPL and created my art show like so:
1 2 type Painting = {id:int;name:string;tags:string} 3 type ArtShow = {id:int;name:string;expectedAttendance:int;paintings:Painting list} 4 5 let painting0 = {id=0; 6 name="Starry Night"; 7 tags="Impressionism;Nature"} 8 let painting1 = {id=1; 9 name="Sunday Afternoon on the Island of La Grande Jatte"; 10 tags="Impressionism;Nature;LeisureActivities"} 11 let painting2 = {id=2; 12 name="Dogs Playing Poker"; 13 tags="Modernism;LeisureActivities"} 14 let paintings = [painting0;painting1;painting2] 15 16 let artShow = {id=0; 17 name="Art Extravaganza"; 18 expectedAttendance=1000; 19 paintings=paintings} 20
I then needed a way of uniquely identifying the tags. Enter the goodness of piping and high order functions:
1 let tagSet = artShow.paintings |> Seq.map(fun p -> p.tags) 2 |> Seq.collect(fun t -> t.Split(';')) 3 |> Seq.groupBy(fun t -> t) 4 |> Seq.map(fun (id,t) -> id, t |> Seq.length) 5
I then needed a way of assigning number of people to tags. Easy enough (this could have been part of the code block above but I split it for illustrative purposes)
1 let visitorsPerTag = artShow.expectedAttendance / (tagSet |> Seq.length) 2 let tagSet' = tagSet |> Seq.map(fun (id,c) -> id, visitorsPerTag/ c )
And then a function that calculates the number of expected visitor based on that the individual painting:
1 let tagModifier(painting: Painting) = 2 let tags = painting.tags.Split(';') 3 tags |> Seq.map(fun pt -> tagSet' |> Seq.find(fun(t,c) -> pt = t)) 4 |> Seq.sumBy(fun(t,c) -> c )
And running it against my show’s paintings gives me the expected values:
1 artShow.paintings |> Seq.map(fun p -> p, tagModifier(p)) 2
So this is why I love F#. The REPL and the language helped me reason and solve the problem. You can see the gist here.
After note: I sent the same challenge to some C# devs I know about how they would reason and then code the answer. No one took me up on it.