Expressing Business Logic With F#'s Partial Active Patterns
Checking the state of the system and performing a requested action only if certain conditions met, is an indispensable requirement in most of the software we write. Though it is a straightforward thing to do, things will get complex/messier when we need to check multiple conditions.
Recently I encountered such a situation while developing a sample application for my fsharp book. Initially, I solved it with a typical nested if else
and then I improved it by applying fsharp’s Partial Active Patterns.
In this blog post , I will be sharing what exactly I did and how it can help you to write an elegant code in fsharp.
Business Domain
Let’s start with a description of the problem domain that we are going to solve.
When an individual visits a restaurant, he can place an order, specifying the list of foods he wants. The foods he ordered will be prepared by the chef and then it will be served.
Preparing a food should satisfy the following conditions
- The Food should be a part of the order
- It should not be prepared already
- It should not be served already
Serving a food should satisfy the following conditions
- The Food should be a part of the order
- It should be prepared already
- It should not be served already
If serving a food completes the order, we should tell that the order is served otherwise we should tell that there are some other foods to be served.
Starting with the types
Let’s start with the types that are needed to solve our problem. If you would like to know why we need to start with the types do check out this wonderful blog post by Tomas Petricek.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The names of the types and its properties clearly describe what they represent, so let’s get to the crux of the problem straight
Prepare Food
1 2 3 4 5 6 7 8 9 10 11 |
|
The if else
conditions represent the criteria mentioned in the business domain and for the sake of simplicity we are just writing the action in the console.
Serve Food
Let’s move on to serving food
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
This is called Arrow Anti Pattern and it is obvious that this code is hard to understand and maintain.
It can be improved by using the techniques mentioned in this StackExchange’s answers and also by using the specification pattern from the OO World.
Though the specification pattern solves the problem, it has a lot of code! No offense here but it can be done in a better way.
F# Active Patterns
Active Patterns allow programmers to wrap arbitrary values in a union-like data structure for easy pattern matching. For example, its possible wrap objects with an active pattern, so that you can use objects in pattern matching as easily as any other union type. - F# Programming Wiki
Let’s begin by defining a partial active pattern for NonOrderedFood
and UnPreparedFood
1 2 3 4 5 6 7 8 9 |
|
Then for AlreadyPreparedFood
and AlreadyServedFood
1 2 3 4 5 6 7 8 9 |
|
Finally,
1 2 3 4 5 6 |
|
With this in place we can rewrite serveFood
function as
1 2 3 4 5 6 7 8 9 10 11 |
|
The goal is to have code that scrolls vertically a lot… but not so much horizontally. - Jeff Atwood
Now the code expressing the business logic more clearly
To refactor prepareFood
to use the parial active patterns we need one more. So, let’s define it
1 2 3 4 |
|
Now we all set to refactor prepareFood
and here we go
1 2 3 4 5 6 7 8 9 |
|
Awesome!
You can get the complete code in my github repository
Summary
F# is excellent at concisely expressing business and domain logic - ThoughtWorks Tech Radar 2012