What is a total function?
A total function returns an output for every single possible input. It is originally a mathematical term talking about mathematical functions, but it can also be applied to functions in programming.
In contrast, a partial function is a function that doesn't produce an output for every single possible input, i.e. it is not a total function.
Example of a total function in F#
List.rev is a total function. It has the type
'T list -> 'T list. It takes a list and returns a reversed version of that list. Here are a few examples:
List.rev (: int list) // returns  List.rev [ 1 ] // returns [ 1 ] List.rev [ 1; 2 ] // returns [ 2; 1 ]
It should be obvious here that there is no possible input list that could not be reversed, and therefore this is a total function.
Example of a partial function
List.head is not a total function. Its type is
'T list -> 'T. It takes a list and returns the first item in that list:
List.head [ 1; 2 ] // returns 1 List.head [ 1 ] // returns 1 List.head (: int list) // throws an exception: The input list was empty.
The last example above shows that if we give
List.head an empty list, it throws an exception instead of returning a result of type
'T. So if the input is an empty list, then there is no output value, which makes it a partial function.
Benefits of total functions
The main benefit of a total function is that it won't throw runtime exceptions. If an exception is thrown that isn't specifically handled somewhere, then your program will fail in some sense: A console program would crash out or a web request would return an error response. Sometimes, this is actually what you want to happen, but in many cases you would prefer to know in advance what could go wrong and specifically handle those cases. There's a good chance that you won't consider all of the edge cases of a partial function that would throw an exception, especially if it's not a simple function.
Making partial functions total
If we look at the
List.head example again. Is there a way that we can change this into a total function instead? Luckily, F# already has a total version called
List.tryHead. This has a slightly different type signature:
'T list -> 'T option. The important difference is that it returns
'T option instead of
'T. Now let's look at the same examples as before:
List.tryHead [ 1; 2 ] // returns Some 1 List.tryHead [ 1 ] // returns Some 1 List.tryHead (: int list) // returns None
This time, the non-empty list inputs return
Some 1 and the empty list input returns
None. These are all valid return values and there are no exceptions thrown.
Now, as the caller of this function the type system forces me to think about both cases and decide what to do for each one. As soon as they see that the return type is an
option, they need to consider the fact that that the list might be empty.
Prefer total functions
There will always be times when we need to throw exceptions but it's best to start from the position of preferring total functions, especially in a functional-first programming language like F#. This means both choosing the total versions of library functions and writing your own functions so that they're total.