Last week’s article introduced the Either class as an alternative to exceptions that makes it easy for functions to declare their error results in addition to their success results and for callers of those functions to handle both results. Today we’ll go further by linking together multiple functions to handle all the error cases almost transparently. Read on to learn how to make the most out of Either!

Let’s write a tiny app in today’s article. The job of the app is to allow a user to change their password. To do this, the app takes the following steps:

  1. Ensure the password isn’t empty
  2. Ensure the password doesn’t contain only letters
  3. Clean the password by trimming whitespace from the start and end
  4. Update the database with the new password

First, let’s write functions for each of these steps. The first one ensures that the password isn’t empty. An Either<string, Error> is returned with either the input string as success or an Error as failure.

Either<string, Error> PasswordNotEmpty(string password)
{
	return string.IsNullOrEmpty(password)
		? new Either<string, Error>(Error.Empty)
		: new Either<string, Error>(password);
}

Next is the function to validate that the password isn’t all letter characters. It’s pretty much the same as the “not empty” function:

Either<string, Error> PasswordNotAllLetters(string password)
{
	return password.All(char.IsLetter)
		? new Either<string, Error>(Error.OnlyLetters)
		: new Either<string, Error>(password);
}

Next there’s a very simple function to clean the password by stripping whitespace from the start and end:

string CleanPassword(string password)
{
	return password.Trim();
}

Finally a function to save the password to the database. We’ll just use Unity’s PlayerPrefs:

void UpdateDatabase(string password)
{
	PlayerPrefs.SetString("password", password);
}

So how do we string all of these actions together? Let’s start out simply by using the Match extension function from the last article:

void UpdatePassword(string password)
{
	Func<Error, int> handleError = err =>
	{
		Debug.LogError("failure due to error: " + err);
		return 1;
	};
	PasswordNotEmpty(password).Match(
		p => PasswordNotAllLetters(p).Match(
			p2 => {
				var p3 = CleanPassword(p2);
				UpdateDatabase(p3);
				Debug.LogFormat(
					"saved as \"{0}\". Retrieved from DB: \"{1}\"",
					p3,
					GetPassword()
				);
				return 0;
			},
			handleError
		),
		handleError
	);
}

Yuck! There are several problems with this code. It’s hard to look at it and tell the order of the steps. This is partly because the steps are mingled together with the success and error handling. Some Match calls get in the way too, as does a lot of indentation.

Nested lambdas necessitate more and more temporary variable names (p, p2, p3) so it’s easy to make a mistake and use the wrong one. Are there better names? Possibly, but now you’re spending your time thinking up names for temporary variables that you’d rather just overwrite and forget.

Match returns a value so each case is required to return something. There’s nothing natural that we want to return though, so we settle on just returning integers. That too gets in the way and readers of the code will probably wonder if there’s any significance to those values.

Finally, there are multiple Match calls and therefore multiple places where errors need to be handled. To avoid duplicating the code, we need to use yet-another lambda in each of those cases. It has an even more confusing int return value just so it fits into the parameters to Match.

That’s a lot of problems with this approach, so let’s start to solve them one at a time. First, let’s find a way to combine the validation steps so that there’s just one function to call instead of two. Just like the individual validation functions, it should take the password as a string and return an Error. If we were to write it manually, here’s how it’d look:

Either<string, Error> ValidatePassword(string password)
{
	return PasswordNotEmpty(password).Match(
		p => new Either<string, Error>(PasswordNotAllLetters(p)),
		e => new Either<string, Error)(e)
	);
}

Using this we could rewrite UpdatePassword to be simpler:

void UpdatePassword(string password)
{
	ValidatePassword(password).Match(
		p => {
			var p2 = CleanPassword(p);
			UpdateDatabase(p2);
			Debug.LogFormat(
				"saved as \"{0}\". Retrieved from DB: \"{1}\"",
				p3,
				GetPassword()
			);
			return 0;
		},
		err =>
		{
			Debug.LogError("failure due to error: " + err);
			return 1;
		}
	);
}

Notice that we’ve gotten rid of some nesting, a temporary (p3), and the handleError lambda. Progress!

It’d be better if ValidatePassword could be used for more than just these two functions, so let’s try to figure out a way to to that. Ideally, we’d like to take a string parameter, pass it to the first function and then pass the return valid of the first function to the second function. Then keep doing that for the rest of the functions. Something like this:

public static class EitherUtils
{
	public static Func<TA, TB>
		Combine<TA, TB>(
			Func<TA, TB> firstFunc,
			params Func<TB, TB>[] moreFuncs
		)
	{
		return arg =>
		{
			var ret = firstFunc(arg);
			foreach (var func in moreFuncs)
			{
				ret = func(ret);
			}
			return ret;
		};
	}
}

We could use it like this:

var doubleThenSquareThenNegate = EitherUtils.Combine(
	x => x * 2,
	x => x * x,
	x => -x
);
doubleThenSquareThenNegate(3); // -36

Unfortunately, it won’t work with PasswordNotEmpty and PasswordNotAllLetters because the return type of PasswordNotEmpty is Either<string, Error> and PasswordNotAllLetters takes a string. However, we could make a version of PasswordNotAllLetters that took an Either<string, Error> and if it was an Error then it would skip its validation and just return the Error it was given. Like this:

Either<string, Error> PasswordNotAllLetters(Either<string, Error> e)
{
	return e.Match(
		password => password.All(char.IsLetter)
			? new Either<string, Error>(Error.OnlyLetters)
			: new Either<string, Error>(password),
		err => err
	);
}

Rather than changing PasswordNotAllLetters directly so it looks like that, let’s make a function that takes PasswordNotAllLetters and returns a version that does that. We’ll call it Bind:

public static class EitherUtils
{
	public static Func<Either<TA, TC>, Either<TB, TC>>
		Bind<TA, TB, TC>(
			Func<TA, Either<TB, TC>> func
		)
	{
		return e => e.Match(
			a => func(a),
			c => new Either<TB, TC>(c)
		);
	}
}

Now we can use Combine and Bind to make ValidatePassword out of PasswordNotEmpty and PasswordNotAllLetters like this:

var validatePassword = EitherUtils.Combine(
	PasswordNotEmpty,
	EitherUtils.Bind<string, string, Error>(PasswordNotAllLetters)
);
 
validatePassword(""); // Error.Empty
validatePassword("abc"); // Error.OnlyLetters
validatePassword(" abc123 "); // " abc123 "

It’s unfortunate, but the compiler needs us to specify the type parameters to Bind because it can’t figure them out. Actually, the compiler can’t figure out how to make PasswordNotEmpty into a Func in this case either so this won’t compile. Instead, we need another helper function to make the Func:

public static class EitherUtils
{
	public static Func<TA, Either<TB, TC>> Identity<TA, TB, TC>(Func<TA, Either<TB, TC>> func)
	{
		return func;
	}
}

We use it like so, remembering to specify the type parameters to help out the compiler:

var validatePassword = EitherUtils.Combine(
	EitherUtils.Identity<string, string, Error>(PasswordNotEmpty),
	EitherUtils.Bind<string, string, Error>(PasswordNotAllLetters)
);

Next we’d like to be able to use Combine to add the CleanPassword step. It just takes a string and returns a string, so it’s not using Either as a parameter or a return type. We could make that change and add unnecessary complication to the function, but it’s better to create a new helper function that will convert it to a function that returns an Either. Let’s call that ReturnEitherLeft:

public static class EitherUtils
{
	public static Func<TA, Either<TA, TB>> ReturnEitherLeft<TA, TB>(Func<TA, TA> func)
	{
		return arg => new Either<TA, TB>(func(arg));
	}
}

Now we can use it to make part of what we need:

var part = EitherUtils.ReturnEitherLeft<string, Error>(CleanPassword);
var e = part("abc"); // returns Either<string, Error>

Now that we have a function just like PasswordNotEmpty and PasswordNotAllLetters in that it takes a string and returns an Either. All we need to do is use Bind on it like we did with PasswordNotAllLetters:

var validatePassword = EitherUtils.Combine(
	EitherUtils.Identity<string, string, Error>(PasswordNotEmpty),
	EitherUtils.Bind<string, string, Error>(PasswordNotAllLetters),
	EitherUtils.Bind<string, string, Error>(
		EitherUtils.ReturnEitherLeft<string, Error>(CleanPassword)
	)
);

For the final phase we need to incorporate UpdateDatabase. It’s kind of like CleanPassword, except that it returns void. Let’s make yet-another function to convert it to return the input string instead of void:

public static class EitherUtils
{
	public static Func<T, T> ReturnParam<T>(Action<T> func)
	{
		return arg => {
			func(arg);
			return arg;
		};
	}
}

Now we can use it to make a function that takes and returns a string:

var part = EitherUtils.ReturnParam<string>(UpdateDatabase);
part("abc"); // returns "abc"

Now that it’s just like CleanPassword we can use ReturnEitherLeft and Bind to make it eligible to be added to the chain of functions passed to Combine. Putting it all together we get a new version of UpdatePassword:

using EU = EitherUtils;
 
void UpdatePassword(string password)
{
	var op = EU.Combine(
		EU.Identity<string, string, Error>(PasswordNotEmpty),
		EU.Bind<string, string, Error>(PasswordNotAllLetters),
		EU.Bind<string, string, Error>(
			EU.ReturnEitherLeft<string, Error>(CleanPassword)
		),
		EU.Bind<string, string, Error>(
			EU.ReturnEitherLeft<string, Error>(
				EU.ReturnParam<string>(UpdateDatabase)
			)
		)
	);
 
	Debug.LogFormat(
		"\"{0}\": {1}",
		password,
		op(password).Match(
			l => "saved as \"" + l + "\". Retrieved from DB: \"" + GetPassword() + "\"",
			r => "failure due to error: " + r
		)
	);
}

Unlike the first version, this one clearly lists the steps (plus some wrapper code). You can read it from top to bottom: PasswordNotEmpty, PasswordNotAllLetters, CleanPassword, UpdateDatabase. There are no lambdas involved except for the final Debug log that formats the output. There are no forced return values from Match calls, which there are none of either.

Here’s the result of a test run:

UpdatePassword(""); // "": failure due to error: Empty
UpdatePassword("abc"); // "abc": failure due to error: OnlyLetters
UpdatePassword(" abc123 "); // " abc123 ": saved as "abc123". Retrieved from DB: "abc123"

Not only does it work, but we now have a reusable set of tools to adapt and chain together any other functions we want. You can download Either.cs on GitHub.

This concludes the series on Either as an alternative to exceptions in C#. What do you think of it? Do you still prefer exceptions? Share your thoughts on error handling strategies in the comments!