Basics 21 - Recursive Functions
What are Recursive Functions
Recursive functions are the ones that call themselves. Recursive functions are commonly used in F#.
The keyword rec
is used for creating recursive functions:
let funcName = ....
let rec funcName = ...
Here's an example to calculate the sum of a given list:
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0
Here's what's going on:
Pattern matching is used
If the list is empty, you hit
[]
case- The function returns 0
If the list isn't empty, you hit
head :: tail
casehead
is the value of the first itemtail
is the remaining list, the list after the first itemhead + sum tail
is where recursion happenssum
function is called again withtail
This goes on and on till the terminal condition isn't met,
[]
case
Recursive Functions and Stack Overflow
Pay attention to head + sum tail
in the previous example. The function sum
is calling sum
again. When a function calls another function, the called function is stacked on top of the caller. There is a practical limit to this.
Consider this example:
// int list -> int
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0
sum [ 1; 2; 3; 4; 5 ] |> printfn "%d" // 15
sum [ 1..100 ] |> printfn "%d" // 5050
sum [ 1..1000 ] |> printfn "%d" // 500500
sum [ 1..10000 ] |> printfn "%d" // 50005000
sum [ 1..100000 ] |> printfn "%d" // crash... stack overflow
For 100,000 items in the list, too much memory is required for stack growth.
You won't frequently counter lists with 100,000 items, but a call like sum [ 1..1000 ]
, which is with 1,000 items is also problematic. It's a call depth of 1,000, which will require a considerable amount of memory.
Understanding Tail Recursion
Going back to the previous example:
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0
In case of head :: tail
, the expression is head + sum tail
. This is effectively saying:
Solve the expression
sum tail
And then, do
head + <whatever the result is>
This will happen for all the items. In other words, stack space (for function call) is required for each item in the supplied list. This is highly inefficient, and for large lists, it's impossible.
To solve this, tail recursion is used. The basic idea is:
When the function calls itself, there should be no need of saving its state (the state of the currently running/called instance of the function)
With
head + sum tail
state is maintainedWhen saving the state is not required F# compiler can utilize tail recursion
Here's the optimized version of the same function:
// int list -> int
let safeSum list =
let rec inner l total =
match l with
| head :: tail -> inner tail (head + total)
| [] -> total
inner list 0
safeSum [ 1; 2; 3; 4; 5 ] |> printfn "%d" // 15
safeSum [ 1..100 ] |> printfn "%d" // 5050
safeSum [ 1..1000 ] |> printfn "%d" // 500500
safeSum [ 1..10000 ] |> printfn "%d" // 50005000
safeSum [ 1..100000 ] |> printfn "%d" // 705082704
safeSum [ 1..1000000 ] |> printfn "%d" // 1784293664
This technique typically involves 3 ideas:
An inner recursive function (
inner)
An accumulated value:
updated per cycle (
head + total
)passed to the inner function (
inner tail (head + total)
)the final result at the terminal condition (
[] -> total
)
The outer function triggers the chain with an initial value accumulated value (
inner list 0
)
Here's the function for finding min value from a list:
let min list =
if List.length list = 0 then
raise (System.ArgumentException("Empt list provided!"))
else
let rec loop list acc =
match list with
| head :: tail -> loop tail (if head < acc then head else acc)
| [] -> acc
loop list Int32.MaxValue
Here's the function for finding max value from a list:
// recursive function for finding max
// no stack overflow, tail recursion
let max list =
if List.length list = 0 then
raise (System.ArgumentException("Empt list provided!"))
else
let rec loop list acc =
match list with
| head :: tail -> loop tail (if head > acc then head else acc)
| [] -> acc
loop list Int32.MinValue
Here's an example of using StringBuilder
to convert char list
to string
:
open System.Text
let charListToString list =
let rec inner (l: char list) (sb: StringBuilder) =
match l with
| head :: tail -> inner tail (sb.Append(head))
| [] -> sb.ToString()
inner list (StringBuilder())
charListToString [ 'a'; 'b'; 'c'; 'd'; 'e' ] |> printfn "%A" // "abcde"
If you have reached so far, congratulations.
Keep reading!