Skip to main content

Either

Either is type that represents the existence of one of two types. An instance of Either is an instance of Left or Right.

Motivation

While Option is a great weapon to have in our functional toolbelt, sometimes it isn't enough. Sometimes it makes sense to provide additional information when our functions don't follow the "happy path". For Options, the happy path can be considered returning Some(...) while the failure path would be returning None. But consider the example below:

User Creation using Option
final class User {
final String name;
final String alias;
final int age;

const User(this.name, this.alias, this.age);
}

Option<User> userOption(String name, String alias, int age) => (
Option.when(() => name.isEmpty, () => name),
Option.when(() => alias.isEmpty, () => alias),
Option.when(() => age >= 18, () => age),
).mapN(User.new);

Reading the code, we can see that a new user requires a non-empty name and a non-empty alias. Let's see what happens when we try to create a few users:

final create1 = userOption('Jonathan', 'Jon', 21); // Some(User(...))
final create2 = userOption('Jonathan', '', 32); // None()
final create3 = userOption('', 'Jon', 55); // None()

We can see that the function works as intended which is great but consider the results returned in the failure case when the user's name and/or alias is empty. They're all None. It would be much better if we could return a reason why the user couldn't be created right? So let's do better using the Either type:

User Creation using Either
Either<String, User> userEither(String name, String alias, int age) {
if (name.isNotEmpty) {
if (alias.isNotEmpty) {
return Right(User(name, alias, age));
} else {
return const Left('Alias is required!');
}
} else {
return const Left('Name is required!');
}
}

final create4 = userEither('Jonathan', 'Jon', 21); // Right(Instance of 'User')
final create5 = userEither('Jonathan', '', 32); // Left(Alias is required!)
final create6 = userEither('', 'Jon', 55); // Left(Name is required!)

Much better! We can now see why the function was unable to create the user in each instance. This would be great information to pass along to the user to help them navigate our application.

info

We said earlier that the "happy" path for Option is Some vs. the failure path of None. So what is the happy/failure paths for Either? Looking at the previous examples, it should become clear that the happy path is Right while the failure path is Left. This is by convention so you could choose to ignore this, but be aware that many combinators in the Either API treat the Right side as the happy path, leading to the statement that Either is "right biased".

Combinators

map

Much like Option the map method on Either will apply a function to the value, so long as the Either is an instance of Right:

const myLeft = Left<int, String>(42);
const myRight = Right<int, String>('World');

String greet(String str) => 'Hello $str!';

final myLeft2 = myLeft.map(greet); // Left(42)
final myRight2 = myRight.map(greet); // Right(Hello World!)

flatMap

Chaining functions that return Either is simple using the Either.flatMap function:

Either<String, User> validateName(User u) =>
Either.cond(() => u.name.isNotEmpty, () => u, () => 'User name is empty!');

Either<String, User> validateAlias(User u) => Either.cond(
() => u.alias.isNotEmpty, () => u, () => 'User alias is empty!');

Either<String, User> validateAge(User u) =>
Either.cond(() => u.age > 35, () => u, () => 'User is too young!');

const candidate = User('Harrison', 'Harry', 30);

final validatedCandidate = validateName(candidate)
.flatMap(validateAlias)
.flatMap(validateAge); // Left(User is too young!)

fold

Lastly, when you want to create a summary value from the Either depending on whether it's a Left or Right, the fold method makes it easy:

final foldLeft = const Left<bool, int>(false).fold(
(boolean) => 'bool value is: $boolean',
(integer) => 'int value is: $integer',
);

As specified in the fold function signature, each function provided must return a value of the same type.

tip

Either has a lot of other useful combinators to make using them easy and expressive! Check out the API to explore them.