Trustbit

View Original

Introduction to Functional Programming in F# – Part 2

Introduction

In the last post, we learned about some of the core features of functional programming in F#. In this post we are going to concentrate on functions.

A function has the following rules:

  • Always returns the same output for the same input

  • Has no side effects

  • Has one input and one output

  • Has immutable input and output

Pure functions that satisfy these rules have many benefits; They are easy to test, are cacheable and parallelizable. However, you application cannot consist only of pure functions as you probably have side effects like user input or persisting to a database.

If you read the previous post, you will remember that we wrote a function that had two parameters. I will show you why that fact and the one input rule are not conflicting. I said in the last post that function signatures are very important; In this post you will see why.

You can do a lot in a single function but you can do more and have better modularity by combining a few smaller functions together. We call this Function Composition.

Function Composition - Theory

We have two functions (f1 and f2) that look like this pseudocode:

See this content in the original post

As the output of f1 matches the input of f2, we can combine them together to create a new function:

See this content in the original post

Treat the >> operator as a general purpose composition operator for two functions.

What happens if the output of f1 does not match the input of f2?:

See this content in the original post

To resolve this, we would create an adaptor function (or use an existing one) that we can plug in between f1 and f2:

See this content in the original post

After plugging f3 in, we can create a new function f4:

See this content in the original post

That is function composition. Let's look at a concrete example.

Function Composition - In Practice

I've taken, and slightly simplified, some of the code from Jorge Fioranelli's excellent F# Workshop: http://www.fsharpworkshop.com. Once you've finished this post, I suggest that you download the workshop (it's free!) and complete it. If you have installed an F# environment as I explained in my previous post, you have everything you need to complete it.

This example has a simple Record type and three functions that we can compose together because the function signatures match up.

See this content in the original post

There a couple of things in this code that we haven't seen before.

The function getPurchases returns a Tuple. Tuples are another of the types in the F# Algebraic Type System (ATS). They are an AND type like the Record type and are used for transferring data without having to define the type. Notice the difference between the definition (Customer * decimal) and the usage (customer, amount). In the tryPromoteToVip function the tuple is decomposed using Pattern Matching into it's constituent parts.

The other new feature is the copy-and-update record expression. This allows you to create a new record instance based on another, usually with some modified data.

There are four ways to compose these functions into a another function:

See this content in the original post

The function upgradeCustomerComposed uses the built-in function composition operator.

See this content in the original post

The upgradeCustomerPiped uses the forward pipe operator (|>). It is the equivalent to the upgradeCustomer function above it but without having to specify the intermediate values. The value from the line above get passed as the last input argument of the next function.

Use the composition operator if you can, otherwise use the forward pipe operator.

It is quite easy to verify the output of the upgrade functions using FSI. Try replacing the upgrade function with any of the others to confirm that they produce the the same results.

See this content in the original post

Record types use Structural Equality which means that if they look the same, they are equal.

Unit

All functions must have one input and one output. To solve the problem of a function that doesn't need an input or produce an output, F# has a type called unit.

See this content in the original post

Unit appears in the function signature as unit but in code you use ().

Multiple Arguments

All functions must have one input and one output but last time we created a function with multiple input arguments:

See this content in the original post

Let's write the function signature slightly differently:

See this content in the original post

The function calculateTotal is a function that takes a Customer as input and returns a function as output that takes a decimal as input and returns a decimal as output. This is called Currying after Haskell Curry, a US Mathematician. It allows you to write functions that have multiple input arguments but also opens the way to a very powerful functional concept; Partial Application.

Let's look at a use case that will show Partial Application; Logging.

See this content in the original post

To partially apply this function, I'm going to define a new function that takes the log function and it's level argument but not the message.

See this content in the original post

The name logError is bound to a function that takes a string and returns unit. So now, instead of using

See this content in the original post

I can use the logError function instead:

See this content in the original post

As the return type is unit, you don't have to let bind the function:

See this content in the original post

When you use functions that return unit in real applications, you will get warned to ignore the output. You do that like this:

See this content in the original post

Partial Application is a very powerful concept that is only made possible because of the concept of Currying input arguments.

Summary

In this post we have covered:

  • Functions

  • Function Composition

  • Tuples

  • Copy-and-update record expression

  • Currying & Partial Application

We have now covered the fundamental building blocks of Functional Programming; Composition of types and functions.

In the next post we will investigate the handling of NULL and Exceptions.

Part 1 Table of Contents Part 3