I recently switched out to a functional based approach on how we communicate absence of value to the function caller. Given in C hash CSharp it’s usually null but that needs more care than letting the compiler guide us.

In this article I will cover the functional equivalent of communicating absence of value functionally using types instead of nulls in languages where its predominant. It will provide you insights into how it’s a more safer way to ensure that when your system crashes the next time it’s definitely not because of a null pointer exception.

Null History

The creator of null himself Tony Hoare (ALGOL) agreed that it was his billion dollar mistake in terms of innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years due to null pointer exceptions where a developer forgets to access value before checking whether it was null or not.

billion dollars

Defensive Approach

Well to be honest we can’t just blame it on the caller. The underlying API should communicate that function can return a null by using documents or type system.

In absence of this information which is usually the case developers have to take a defensive approach of zero trust policy where any input and result from calling into API/function can return null.

public int myFunction(string input) {
	// check input for null
	if (input == null) // throw error or early return

	var result = someNewFunction();
	if (result == null)
		// throw error or early return
}

Nullable Types

C# doesn’t really get much appreciation in terms of how good of a programming language it is in terms of things like:

  • Dependency Injection
  • Nullable Types
  • Pattern Matching
  • Object Inheritance
  • Async Runtime

I have come to like it a lot, for now focusing on nullable types before moving to functional equivalent. Nullable types are mid way of pure null and functional equivalent, given they provide a type system consistent to functional but the associated APIs with nullable types is still filled with foot guns, the compiler will only warn you and not error.

public T? validateInput<T>(T input) {
	if (!input.valid()) return null;
	return input
}

The above example now communicates to caller that we might return a null using ? operator and then can make use of things like .? and ?? which are null conditional and null coalescing operator that make handling of null a little better.

validateInput()?.Run();
var result = validateInput(input) ?? defaultInput();

This is definitely better but it still doesn’t stop the developer from directly accessing members of a null object without using null safety operators which will again throw errors.

Functional Equivalent

The functional equivalent of a null type is usually something called None. The overall type is Option<T>/Optional<T>/Maybe<T> where T is generic or type in case value is something and not None which is one of the enum variants.

A functional equivalent has the requirement that the type should differentiate between a type that can be a two different versions that is without value and with value. The Maybe type was introduced by forefather of functional languages Haskell

data Maybe a = Nothing | Just a

So Maybe has two variants:

  • Nothing which implies absence of value
  • Just which holds value a

Rust also took this up and its Enum supports for allowing things like Option and Result.

fn ValidateInput<T>(input: T) -> Option<String> {
	if (!input.isValid()) return None;
	return Some(input)
}

Now given these languages require you to handle all the possible patterns properly, so the compiler basically makes it quite impossible for you to escape hatch through this without doing covering all branches.

let result = ValidateInput(input);
match result {
	Some(value) => //
	None => //
}

Extracting Value

The Option provides unwrap to extract value if present, otherwise it panics so it should be used with care and after checking for prsence of value. It has several other variants as well to provide a default value or lambda to generate default value.

let result = ValidateInput(input).unwrap_or_else(|| default_value());

The footgun here is user calling unsafe method like unwrap on an optional without checking, but this is a well know anti pattern and if user is sure it won’t be a None it’s fine to use sometimes.

Transforming Values

We can chain Option values with filter and map functions to transform if there is some value inside them or return a None.

let result: Option<int32> = Some(5);
let double: Option<int32> = result.map(|v| 2 * v);

The Option type is a Monad, which basically implies that it is a container of value and we can chain it with other functions that accept an Option type stuff like expect, unwrap, map, filter, etc which are the functors.

fn combine(a: Option<i32>, b: Option<i32>) -> Option<i32> {
	match (a, b) {
		(Some(a_value), Some(b_value)) => Some(a_value + b_value),
		_ => None
	}
}

let result = combine(Some(2), None).map_or(42)

We can make use of ? operator in rust to make things even more succinct rather than checking for each passed input.

fn combine(a: Option<i32>, b: Option<i32>) -> Option<i32> {
	a? + b?
}

Option Extensions

Languages like C#, Java, C++, etc make use of null or nullptr very extensively and they over the period have developed ways to align more on the functional approach.

LanguageExtension
C#Nullable Type
JavaOptionals
C++Optional

Both Java and C++ optional were recent additions and provide compiler support for dealing with these types. In case your code makes use of null/nullptr maybe its time to move away and take up this refactor for safety of your production system.

optional<int> o = maybe_return_an_int();
if (o.has_value()) { /* ... */ }

NOTE: NULL isn’t null, for more see Memory Management

As I mentioned C# nullable types are warnings and not compiler errors so even with them developers can still manage to kill the system. Initially we explored them, but then made a choice to opt for a package, CSharpFunctionalExtensions which provides support for Maybe type.

Conclusion

The reason that made Tony opt in for something like null is because they are very simple and at this point we all are very much scared familiar with them. Going for something functional comes with cost of knowing a little about the mathematical and academia aligned field of functional programming.

There are companies making use of functional languages Jane Street uses OCaml, Bloomberg uses OCaml, Fly uses Elixir, Databricks uses Scala, etc so they are very much a viable safe alternative.

zero trust null would very much work in an ideal world where API authors add documentation in case they are returning nulls and the developers play defensive, ensuring all validations are in place before utilising the returned values. But its not an ideal world developers make mistake and have to be guided by the compiler.

Thanks for reading this am diving more into functional programming based on my manager’s recommendation and did Advent of Code in Elixir this time.