Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features.

Introduction

This series of posts will introduce you to the world of functional programming (FP) in F#. Rather than start with theory or a formal definition, I thought that I'd start with a typical business problem and look at how we can use some of the functional programming features of F# to solve it.

Setting up your environment

  1. Install F# (Installing the dotnet core SDK will install F#)

  2. Install VSCode with the ionide extension (VS2019 or JetBrains Rider will work as well)

  3. Open VSCode and open a blank folder to store your code.

  4. Add a new file and name it first.fsx

  5. Type 1 = 1 into the file.

  6. Highlight the code and press ALT + ENTER

  7. You should see F# Interactive (FSI) open in your Terminal and be able to see 'val it : bool = true'

If all is OK, let's take a look at a simple business Use Case and see how we can use functional programming in F# to implement it.

Stage 1 - The Problem

This problem comes from a post by Chris Roff (https://medium.com/@bddkickstarter/functional-bdd-5014c880c935) where he looks at using F# and BDD together.

Feature: Applying a discount
Scenario: Eligible Registered Customers get 10% discount when they spend £100 or more

Given the following Registered Customers
|Customer Id|Is Eligible|
|John       |true       |
|Mary       |true       |
|Richard    |false      |

When <Customer Id> spends <Spend> Then their order total will be <Total>

Examples:
|Customer Id|   Spend|   Total|
|Mary       |   99.00|   99.00|
|John       |  100.00|   90.00|
|Richard    |  100.00|  100.00|
|Sarah      |  100.00|  100.00|

Along with some examples showing how you can verify that your code is working correctly are a number of domain-specific words and concepts. I want to show how we can represent some of these in our code. We will start of with something simple but naive and then we'll see how F# can help us make it much more domain specific and as an added benefit, less susceptible to bugs.

Stage 2 - Initial Version:

Along with simple datatypes like string, decimal and boolean, F# has a powerful Algebraic Type System (ATS). At this stage, think of these types as data structures to use in functions. The first of the types we will use is the Record Type. We can define our customer like this:

type Customer = {
    Id : string
    IsEligible : bool
    IsRegistered : bool
}

To create an instance of a customer we would write the following below the type definition:

let fred = { Id = "Fred"; IsEligible = true; IsRegistered = true }

By using the let keyword, we have bound the name 'fred' to the this instance of a Customer. It is immutable (cannot be changed).

Delete fred as we don't need him.

Below the Customer type, we need to create a function to calculate the total. The function should take a Customer and a Spend (decimal) and return the Total (decimal).

let calculateTotal (customer:Customer) (spend:decimal) : decimal =
    let discount = if customer.IsRegistered && customer.IsEligible && spend >= 100.0M then (spend * 0.1M) else 0.0M   
    let total = spend - discount
    total

There are a few things to note about functions:

  • We have used 'let' again to define the function and inside the function to define discount and total.

  • There is no container as functions are first-class citizens.

  • The return type is to the right of the input arguments.

  • No return keyword. The last line is returned.

  • Significant whitespace (Tabs are not allowed).

  • The function signature is Customer -> decimal -> decimal. The item at the end of the signature (after the last arrow) is the return type of the function.

Function Signatures are very important; Get used to looking at them.

The F# Compiler uses a feature called Type Inference which means that most of the time it can determine types through usage without you needing to explicitly define them. As a consequence, we can re-write the function as:

let calculateTotal customer spend =
    let discount = if customer.IsRegistered && customer.IsEligible && spend >= 100.0M then (spend * 0.1M) else 0.0M   
    spend - discount

I also removed the total binding as I don't think it adds anything to the readability of the function. The function signature is still Customer -> decimal -> decimal.

Highlight the code you've written so far and press ALT + ENTER. This will run this code in F# Interactive (FSI) in the Terminal window.

Now create a customer from our specification and run in FSI:

let john = { Id = "John"; IsEligible = true; IsRegistered = true }

Rather than write a formal test, we can use FSI to run simple verifications for us. We will look at writing proper unit tests later in the series.

let assertJohn = (calculateTotal john 100.0M = 90.0M)

What you should see after running the test in FSI is the following:

val assertJohn : bool = true

Add in the other users and test cases from the specification.

let john = { Id = "John"; IsEligible = true; IsRegistered = true }
let mary = { Id = "Mary"; IsEligible = true; IsRegistered = true }
let richard = { Id = "Richard"; IsEligible = false; IsRegistered = true }
let sarah = { Id = "Sarah"; IsEligible = false; IsRegistered = false }

let assertJohn = calculateTotal john 100.0M = 90.0M
let assertMary = calculateTotal mary 99.0M = 99.0M
let assertRichard = calculateTotal richard 100.0M = 100.0M
let assertSarah = calculateTotal sarah 100.0M = 100.0M

Highlight the new code and press ALT + ENTER. You should see the following in FSI.

val assertJohn : bool = true
val assertMary : bool = true
val assertRichard : bool = true
val assertSarah : bool = true

Your code should now look like this:

type Customer = {
    Id : string
    IsEligible : bool
    IsRegistered : bool
}

let calculateTotal customer spend =
    let discount = if customer.IsRegistered && customer.IsEligible && spend >= 100.0M then (spend * 0.1M) else 0.0M   
    spend - discount

let john = { Id = "John"; IsEligible = true; IsRegistered = true }
let mary = { Id = "Mary"; IsEligible = true; IsRegistered = true }
let richard = { Id = "Richard"; IsEligible = false; IsRegistered = true }
let sarah = { Id = "Sarah"; IsEligible = false; IsRegistered = false }

let assertJohn = calculateTotal john 100.0M = 90.0M
let assertMary = calculateTotal mary 99.0M = 99.0M
let assertRichard = calculateTotal richard 100.0M = 100.0M
let assertSarah = calculateTotal sarah 100.0M = 100.0M

Whilst this code works, I don't like boolean properties representing domain concepts. To this end, we will make Registered/Unregistered explicit in the code.

Stage 3 - Making the Implicit Explicit (1)

Firstly, we create specific Record types for Registered and Unregistered Customers.

type RegisteredCustomer = {
    Id : string
    IsEligible : bool
}

type UnregisteredCustomer = {
    Id : string
}

To represent the fact that a Customer can be either Registered or Unregistered, we will use another of the built-in types in the ATS; the Discriminated Union (DU). We define the Customer type like this:

type Customer =
    | RegisteredCustomer of RegisteredCustomer
    | Guest of UnregisteredCustomer

It is very hard to describe a Discriminated Union to an OOP developer because there is nothing in OOP that is remotely close to them. This reads as "a customer is either a registered customer of type RegisteredCustomer or a guest of type UnregisteredCustomer".

The easiest way to understand a DU is to use it! We have to make changes to the users that we have defined. Firstly the UnregisteredCustomer:

let sarah = Guest { Id = "Sarah" } // Guest of UnregisteredCustomer

Look at how the definition in the DU compares to the binding.

Now let's make the required changes to the RegisteredCustomers:

let john = RegisteredCustomer { Id = "John"; IsEligible = true }
let mary = RegisteredCustomer { Id = "Mary"; IsEligible = true }
let richard = RegisteredCustomer { Id = "Richard"; IsEligible = false }

Changing the Customer type to a DU has an impact on the function. We will need to re-write the discount calculation using another F# feature - Pattern Matching:

let calculateTotal customer spend =
    let discount = 
        match customer with
        | RegisteredCustomer c -> if c.IsEligible && spend >= 100.0M then (spend * 0.1M) else 0.0M
        | Guest _ -> 0.0M
    spend - discount

To understand what the pattern match is doing is matching, compare the match 'RegisteredCustomer c' with how we constructed the users 'RegisteredCustomer { Id = "John"; IsEligible = true }'. In this case, 'c' is a placeholder for the customer instance. The underscore in the Guest pattern match is a wildcard and implies that we don't need access to the instance. Pattern matching against DUs is exhaustive. If you don't handle every case, you will get a warning on the customer in the match saying 'incomplete pattern match'.

We can simplify the logic with a guard clause but it does mean that we need to account for non-eligible Registered customers otherwise the match is incomplete:

let calculateTotal customer spend =
    let discount = 
        match customer with
        | RegisteredCustomer c when c.IsEligible && spend >= 100.0M -> spend * 0.1M
        | RegisteredCustomer _ -> 0.0M
        | Guest _ -> 0.0M
    spend - discount

We can simplify the last two matches using a wildcard like this:

let calculateTotal customer spend =
    let discount = 
        match customer with
        | RegisteredCustomer c when c.IsEligible && spend >= 100.0M -> spend * 0.1M
        | _ -> 0.0M
    spend - discount

The tests don't need to change.

This is much better than the naive version we had before. It is much easier to understand the logic and much more difficult to have data in an invalid state. Does it get better if we make Eligibility explicit as well? Let's see!

Stage 4 - Making the Implicit Explicit (2)

Remove the IsEligible flag from RegisteredCustomer and add EligibleRegisteredCustomer to the Customer DU.

type RegisteredCustomer = {
    Id : string
}

type UnregisteredCustomer = {
    Id : string
}

type Customer =
    | EligibleRegisteredCustomer of RegisteredCustomer
    | RegisteredCustomer of RegisteredCustomer
    | Guest of UnregisteredCustomer
We need to make a change to our function.

let calculateTotal customer spend =
    let discount = 
        match customer with
        | EligibleRegisteredCustomer _ when spend >= 100.0M -> spend * 0.1M
        | _ -> 0.0M
    spend - discount

We no longer need to test for IsEligible and we also no longer need access to the instance, so we can replace the 'c' with a underscore (wildcard).

We make some minor changes to our helpers.

let john = EligibleRegisteredCustomer { Id = "John" }
let mary = EligibleRegisteredCustomer { Id = "Mary" }
Run your code in FSI to check all is still OK.

The state of our code after all of our improvements is:

type RegisteredCustomer = {
    Id : string
}

type UnregisteredCustomer = {
    Id : string
}

type Customer =
    | EligibleRegisteredCustomer of RegisteredCustomer
    | RegisteredCustomer of RegisteredCustomer
    | Guest of UnregisteredCustomer

let calculateTotal customer spend =
    let discount = 
        match customer with
        | EligibleRegisteredCustomer _ when spend >= 100.0M -> spend * 0.1M
        | _ -> 0.0M
    spend - discount

let john = EligibleRegisteredCustomer { Id = "John" }
let mary = EligibleRegisteredCustomer { Id = "Mary" }
let richard = RegisteredCustomer { Id = "Richard" }
let sarah = Guest { Id = "Sarah" }

let assertJohn = calculateTotal john 100.0M = 90.0M
let assertMary = calculateTotal mary 99.0M = 99.0M
let assertRichard = calculateTotal richard 100.0M = 100.0M
let assertSarah = calculateTotal sarah 100.0M = 100.0M

I think that this is a big improvement over where we started but we can do better! We will revisit this in a later post and we will look at Unit Testing where we can make use of the helpers and assertions we've already written.

Summary

We have covered quite a lot in this post:

  • F# Interactive (FSI)

  • Algebraic Type System
    - Record Types
    - Discriminated Union

  • Pattern Matching
    - Guard Clause

  • Let bindings

  • Functions

  • Function Signatures

In the next post, we will start to look at function composition - building bigger functions out of smaller ones.

Postscript

To illustrate the portability of the functional programming concepts we have covered in this post, one of my colleagues, Daniel Weller, wrote a Scala version of the final solution:

sealed trait Customer

case class RegisteredCustomer(id : String) extends Customer
case class EligibleRegisteredCustomer(id : String) extends Customer
case class Guest(id: String) extends Customer

def calculateTotal(customer: Customer)(spend: Double) = {
    val discount = customer match {
        case EligibleRegisteredCustomer(_) if spend >= 100.0 => spend * 0.1
        case _ => 0.0
    }
    spend - discount
}

val john = EligibleRegisteredCustomer("John")
val assertJohn = (calculateTotal (john) (100.0)) == 90.0
Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 12/3/21

Using Discriminated Union Labelled Fields

A few weeks ago, I re-discovered labelled fields in discriminated unions. Despite the fact that they look like tuples, they are not.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 7/21/20

Understanding F# applicatives and custom operators

In this post, Jonathan Channon, a newcomer to F#, discusses how he learnt about a slightly more advanced functional concept — Applicatives.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Headerbild zur automatischen Handschrifterkennung bei Versicherern
Branche

Automatic handwriting recognition for insurers

The recognition of handwriting works "out of the box". In addition, we support our customers in further document classification and extraction of specialized data so that it can be processed automatically.

Referenz

Introduction of Jira to Hamburger Hochbahn

The Hamburger Hochbahn AG controls the development of its new mobility platform "Switchh" via the Atlassian project management tool Jira – introduced, administered and hosted by the TIMETOACT GROUP.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!