Introduction to Functional Programming in F# – Part 5
Introduction
Welcome to the fifth post in this introductory series on Functional Programming in F#. In this post we will be building upon some of the concepts we have learned in previous posts whilst investigating functional collections.
Before We Start
We are going to use the solution we created in the last post of this series: https://trustbit.tech/blog/2019/10/01/introduction-to-functional-programming-in-f-sharp-part-4.
Add Orders.fs, OrderTests.fsx and lists.fsx to the code project and OrderTests.fs to the test project.
The Basics
F# has a hugely deserved reputation for being great for data-centric use cases like finance and data science due in a large part to the power of it's support for data structures and collection handling. In this post we will look at how we can harness some of this power in normal line of business apps.
There are a number of collections in F# that we can make use of but the three primary ones are:
Sequence (Seq) - Lazily evaluated - Equivalent to IEnumerable.
Array - Great for numerics/data science. There are built-in modules for 2d, 3d and 4d arrays.
List - Eagerly evaluated and immutable structure and data. F# specific, not same as List.
Each of these types has a module that contains a wide range of functions including some to convert to/from each other.
In this post we are going to concentrate on the List type and module.
Core Functionality
We will be using lists.fsx for this section. Remember to highlight the code and run it in F# Interactive (FSI).
Create an empty list:
Create a list with five integers:
In this case, we could also do this:
Or we could use a List comprehension:
List comprehensions are really powerful but we are not going to use them in this post.
To add an item to a list, we use the cons operator:
The original list remains unaffected by the new item as it is immutable.
A list is made up of a head (single item) and a tail (list of items). We can pattern match on a list to show this:
We can join (concatenate) two lists together:
As lists are immutable, we can re-use them knowing that there values/structure will never change.
We could also use the concat function on the List module to do the same job as the @ operator:
We can filter a list using an ('a -> bool) function and the filter function from the List module:
We can add up the items in a list using the sum function:
Other aggregation functions are as easy to use but we are not going to look at them here.
Sometimes we want to perform an operation on each item in a list. If we want to return the new list, we use the map function:
If we don't want to return a new list, we use the iter function:
Let's take a look at a more complicated example using map that changes the structure of the output list. We will use a list of tuples (int * decimal) which might represent quantity and unit price.
To calculate the total price of the items, we can use map to convert (int * decimal) list to decimal list and then sum the items:
Note the explicit conversion of the integer to a decimal. F# is strict about types in calculations and does support implicit conversion. In this particular case, there is an easier way to do the calculation in one step:
Folding
A very powerful functional concept that we can use to do similar aggregation tasks (and lots more that we won't cover) is the fold function:
The lambda function uses an accumulator and the deconstructed tuple and simply adds the intermediate calculation to the accumulator. The 0M parameter is the initial value of the accumulator. If we were folding using multiplication, the initial value would probably have been 1M.
To make it clearer what we are trying to do, we could use the ||> operator:
Grouping Data and Uniqueness
Rather than try to explain what the groupBy function does, it will be easier to show you:
To get the list of unique items from the result list, we can use the map function:
There is a built-in collection type called Set that will do this as well:
Note the use of the ofList function to convert a list to a set.
We now have enough information to move onto a practical example of using an F# list.
Practical Example
In this example code we are going to manage an order with an immutable list of items. The functionality we need to add is:
Add an item
Remove an item
Reduce quantity of an item
Clear all of the items
First, create a module called Orders in Orders.fs and add record types for Order and Order Item:
Now we need to add a function to add an item to the order. This function needs to cater for products that exist in the order as well as those that don't. Let's create a couple of helpers bindings to help us get started:
Firstly we need to group the items by the productId:
Then we use the map and sumBy functions to aggregate per product:
Finally, we need to copy and update the order with our newly calculated items:
Remove the helpers for order, newItem and result as we are going to create the following asserts in the OrderTests.fsx file:
We can easily add multiple items to an order:
Add some asserts to the OrderTests.fsx file for the addItems function:
Let's extract the common functionality (group by and map) into a new function:
Run the changes into FSI and the verify using the asserts.
We could simplify/modify the addItem function to the following:
Removing an item can be easily achieved by filtering out the unwanted item by the productId:
Again we write some asserts to verify our new function works as expected:
Reducing an item quantity is slightly more complex. Firstly we add an item with negative quantity, recalculate the items and then filter out any items with a quantity less than or equal to 0:
Again we write some asserts to verify our new function works as expected:
Clearing all of the items is really simple:
Write some asserts to verify our new function works as expected:
You should now convert all of the asserts we have written to real tests in OrderTests.fs in the test project.
For more details on the List module, have a look at the Language Reference in the F# Guide (https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/index).
Summary
In this post we have looked at some of the most useful functions on the List module and we have seen that it is possible to use immutable data structures to provide important business functionality. We are five posts into the series and we haven't mutated anything yet!
In the next post we will look at how to handle streams of data from a json source.
If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.