Partiality and Exception
Partiality and
Exception are not typical topics for a programming article; outside of Framework design or Design by Contract. It isn’t because our functions are partiality and exception free, it’s because code is tested in such a narrow way that tests rarely consider the complete scope of input values.
Our code may have many implicit preconditions that we’ve either never considered or that we never document because the definition of a function doesn't insist on this.
Careful thought of how to avoid partiality and exceptions is the assessment of whether our programs will work reliably across a wide range of input and computation scenarios.
Consider a function that converts an integer to a boolean where
0 is
false and
1 is
true.
C#:
static bool ToBool(int x) {
if (x == 0) return false;
else if (x == 1) return true;
}
The function satisfies the design expectations:
0 becomes
false and
1 becomes
true however the compiler will raise the following error message:
Error: 'ToBool(int)': not all code paths return a value
The compiler picked up that our function doesn't cater for all values of
x.
Changing The Design Specifications
We could change the function to deal with every possible value of
x by applying the
C language rule :
C#:
static bool ToBool(int x) {
if (x == 0) return false;
return true;
}
Except we're now in violation of the design expectations; invalid values of
x return
true
Preconditions
To address the design restriction, we could create a function with a precondition to raise an exception for unsupported values; on the expectation that the caller would encapsulate the call in a
try catch to safely manage any possible exception.
C#:
static bool ToBool(int x) {
if (x > 1 || x < 0) throw new ArgumentException(); ;
if (x == 0) return false;
return true;
}
Whilst this approach arguably works as designed in a synchronous call stack, it poses significant challenges when executed concurrently.
Function Type Honesty
Another issue is that looking at the function signature we're oblivious that this function is a
partial function or that it will raise exceptions.
C#:
static bool ToBool(int x)
This makes these functions dependent on documentation and testing (two areas worryingly prone to lapses):
- A partial function’s requirements must be documented
- Tests must exercise a wide range of inputs to confirm that the output is consistent and expected.
Let’s assume points 1 is satisfied (or at least identified during debugging). We'd still need to satisfy point 2.
Debugging and testing will unlikely validate all scenarios in a non-trivial program; point 2 only excels at validating specific scenarios so unless your tests are extremely comprehensive, it’s likely your users will be able to get one of your functions into a state you've never tested for; increasing the potential for a runtime failure. This of course doesn't even cover the fact that many IDEs and/or code editors do not by default warn about unhandled exceptions; which makes it far easier for mistakes.
Monadic Data Types
The simplest solution to making any partial function into a total function is to change the type signature to include room in the return type to communicate a failure condition. Instead of triggering a fatal error, we can instead communicate the failure condition back to the caller and let them choose how and when to deal with it.
Partial functions including functions with exceptions can converted into a total function via a monadic data type like
Maybe (
a logical disjunction between a valid and invalid results).
C#:
static Maybe<bool> ToBool(int x) {
if (x == 0) return Just(false);
else if (x == 1) return Just(true);
return Nothing();
}
This converts the previous
Partial function into a
Total function.
Total Function is a function defined for all possible input values. It also meets all the requirements of a
pure function.
Monadic Data Types Make Function Types More Honest.
The
Maybe data type also makes the function type more honest, because every input value is catered for and its immediately apparent from the function's type signature; no documentation or comment required.
C#:
static Maybe<bool> ToBool(int x)
The built-in equivalent of the
Maybe monad in C# is the
Option type; for example:
C#:
static bool? ToBool(int x) {
if (x == 0) return false;
else if (x == 1) return true;
return nil;
}
The implementation of the
Option data type in C# has built in implicit conversions; the
bool? return type is syntactic sugar for
Option<bool> and the implicit conversion on the
Option type lifts the
bool return value into a
Option type with a success condition; whereas the
return nil is implicitly converted into a
Option with an error condition.