Trustbit

View Original

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:

See this content in the original post

Create a list with five integers:

See this content in the original post

In this case, we could also do this:

See this content in the original post

Or we could use a List comprehension:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

We can join (concatenate) two lists together:

See this content in the original post

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:

See this content in the original post

We can filter a list using an ('a -> bool) function and the filter function from the List module:

See this content in the original post

We can add up the items in a list using the sum function:

See this content in the original post

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:

See this content in the original post

If we don't want to return a new list, we use the iter function:

See this content in the original post

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.

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

Grouping Data and Uniqueness

Rather than try to explain what the groupBy function does, it will be easier to show you:

See this content in the original post

To get the list of unique items from the result list, we can use the map function:

See this content in the original post

There is a built-in collection type called Set that will do this as well:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

Firstly we need to group the items by the productId:

See this content in the original post

Then we use the map and sumBy functions to aggregate per product:

See this content in the original post

Finally, we need to copy and update the order with our newly calculated items:

See this content in the original post

Remove the helpers for order, newItem and result as we are going to create the following asserts in the OrderTests.fsx file:

See this content in the original post

We can easily add multiple items to an order:

See this content in the original post

Add some asserts to the OrderTests.fsx file for the addItems function:

See this content in the original post

Let's extract the common functionality (group by and map) into a new function:

See this content in the original post

Run the changes into FSI and the verify using the asserts.

We could simplify/modify the addItem function to the following:

See this content in the original post

Removing an item can be easily achieved by filtering out the unwanted item by the productId:

See this content in the original post

Again we write some asserts to verify our new function works as expected:

See this content in the original post

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:

See this content in the original post

Again we write some asserts to verify our new function works as expected:

See this content in the original post

Clearing all of the items is really simple:

See this content in the original post

Write some asserts to verify our new function works as expected:

See this content in the original post

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.

Part 4 Table of Contents Part 6