Introduction to Functional Programming in F# – Part 11

Introduction

In this post we are going to look at recursive functions, that is functions that call themselves in a loop. We will start with a naive implementation and then make it more efficient using an accumulator. As a special treat, I will show you a way of writing FizzBuzz using this technique.

Setting Up

All of the code in this post can be run using FSI. I recommend using VSCode with the ionide plugin.

Solving The Problem

We are going to start with a naive implementation of the factorial function (!):

5! = 5 * 4 * 3 * 2 * 1 = 120

To create a recursive function, we use the 'rec' keyword and we would create a function like this:

let rec fact n = // int -> int
    match n with
    | 1 -> 1
    | n -> n * fact (n-1)

You'll notice that we have two case, 1 and greater than 1. When n is 1, we return 1 and end the recursion but if it is greater than 1, we return the number multiplied by the factorial of the number - 1 and continue the recursion. If we were to write out what happens, it would look like this:

fact 5 = 5 * fact 4
       = 5 * (4 * fact 3)
       = 5 * (4 * (3 * fact 2))
       = 5 * (4 * (3 * (2 * fact 1)))
       = 5 * (4 * (3 * (2 * 1)))
       = 5 * (4 * (3 * 2))
       = 5 * (4 * 6)
       = 5 * 24
       = 120

This is a problem because you can't unwind the calculations until you've calculated all of the iterations but you also need to store all of the parts as well. This means that the larger n gets, the more memory you need to perform the calculation and it can also lead to stack overflows. We can solve this problem with Tail Call Optimisation.

Tail Call Optimisation

The approach we are going to take is to use an accumulator. The first thing that we need to do is to re-write our factorial function:

let fact n =
    let rec go n acc =
        match n with
        | 1 -> acc
        | _ -> go (n-1) (acc * n)
    go n 1

We leave the public interface of the function intact and create a function enclosed in the outer one to do the recursion. We also need to add a line to start the recursion. You'll notice that we've added an additional parameter which is the accumulator. As we are multiplying, this need to be 1 in this case. Lets write out what happens when we run this function as we did the previous version:

fact 5 = go 5 1
       = go 4 5
       = go 3 20
       = go 2 60
       = go 1 120
       = 120

This is much simpler than the previous example, requires very little memory and is extremely efficient.

We can also solve factorial using the reduce and fold functions from the List module.

let fact n = // int -> int
    [1..n] |> List.reduce (*)

let fact n = // int -> int
    [1..n] |> List.fold (*) 1

Now that we've learnt about tail call optimisation using an accumulator, let's look at a harder example, the Fibonacci Sequence.

Expanding The Accumulator

The Fibonacci Sequence is a simple list of numbers where each value is the sum of the previous two:

0, 1, 1, 2, 3, 5, 8, 13, 21, ...

Let's start with the naive example to calculate the nth item in the sequence:

let rec fib (n:int64) =
    match n with
    | 0L -> 0L
    | 1L -> 1L
    | s -> fib (s-1L) + fib (s-2L)

If you want to see just how inefficient this is, try running fib 50L. It will take almost a minute on a fast machine! Let's have a go at writing a more efficient version that uses tail call optimisation:

let fibL (n:int64) =
    let rec go n (a,b) =
        match n with
        | 0L -> a
        | 1L -> b
        | n -> go (n-1L) (b,a+b)
    go n (0L,1L)

Let's write out what happens (ignoring the type):

fibL 5 = go 5 (0,1)
       = go 4 (1,1+0)
       = go 3 (1,1+1)
       = go 2 (2,1+2)
       = go 1 (3,2+3)
       = 3

The 5th item in the sequence is indeed 3 as the list starts at index 0. Try running fibL 50L - It should return almost instantaneously.

We'll now continue on our journey to find as many functional ways of solving FizzBuzz as possible. :)

FizzBuzz Using Recursion

We start with a list of rules that we are going to recurse over:

let fizzRules = 
    [
        (fun i -> if i % 3 = 0 then "Fizz" else "")
        (fun i -> if i % 5 = 0 then "Buzz" else "")
    ]

We then write our fizzbuzz function using tail call optimisation. In this case the accumulator will use string concatenation.

let fizzBuzz rules i =
    let rec loop rl s =
        match rl, s with
        | [], "" -> i.ToString()
        | [], _ -> s
        | h::t, _ -> loop t (s + h i) // h is a function from fizzRules
    loop rules ""

Finally we run the function and print the result:

[ 1 .. 105 ]
|> List.map (fizzBuzz fizzRules)
|> List.iter (printfn "%s")

This is quite a nice extensible approach to the FizzBuzz problem as adding 7-Bazz is as easy as adding another rule.

Other Uses Of Recursion

The examples we've used are necessarily simple to concentrate on tail call optimisation using an accumulator but what would we really use recursion for in a real application? Recursion is great for handling hierarchical data like the file system or XML, converting between flat data and hierarchies and for event loops or loops where there is no defined finish.

Further Reading

As always, Scott Wlaschin's excellent site has many posts on the topic (https://fsharpforfunandprofit.com/posts/recursive-types-and-folds-3b/#series-toc).

Conclusion

In this post we have had a look at recursion in F#. In particular, we have looked at how to use accumulators with tail call optimisation to make recursion more efficient.

This may be the last post in this series. Either way, look out for a new series on creating websites and APIs in F# with Giraffe - Coming soon!

If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.

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 7

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

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 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 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 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 5/18/22

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. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

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.

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/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 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 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 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/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/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 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 5/1/21

Ways of Creating Single Case Discriminated Unions in F#

There are quite a few ways of creating single case discriminated unions in F# and this makes them popular for wrapping primitives. In this post, I will go through a number of the approaches that I have seen.

Mit Google Workspace den digitalen Arbeitsplatz ermöglichen Sales Freshsales
Service 8/17/23

Gemini

Experience Gemini in Google Cloud and revamp the way you work. Discover innovative Generative AI functions for maximum efficiency.

Headerbild IT Asset Management
Service

IT Asset Management – Reducing Costs and Risks Sustainably

We help customers to increase the transparency of IT assets in use, identify potential savings for hardware and software, and avoid compliance risks at the same time.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!