Core Functionality
Learn about the different functionality that is common to both the Option and Result types.
Introduction
While Option<T>
and Result<T, E>
serve different purposes (absence vs. success/failure), they share a common operational model. If you are fluent with either the Option<T>
or Result<T, E>
already, you're 90% of the way to mastering the other.
The core APIs defined below work the same across both types, differing only in terms of semantics.
Creation
Both Option
and Result
types have factory methods that allow you to create an instance of the monad in one of it's two states.
Option<string> some = Option.Some("Hello Bees!");
Option<string> none = Option.None<string>();
Use Try
to safely capture potentially exception-throwing logic inside a monadic wrapper. This is useful if you want to begin a monadic chain from a method you do not have control over.
Option<User> maybeUser = Option.Try(() => GetCurrentUser());
If the GetCurrentUser
call throws, the exception is caught and logged via your configured exception logger, and you get back a None<User>
instance.
Transform
Transformations are the bread and butter of monadic chains. They are used to transform the value inside the monadic wrapper and perform operations on them.
Map
Map
is the most common transform. Use Map
to apply a transformation to the contained value if it is present or successful.
Option<string> maybeName = Option.Some("Henry Crabgrass");
Option<int> maybeLength = maybeName.Map(name => name.Length);
More Transforms
There are additional transform methods specific to the Option<T>
and Result<T, E>
types.
State Checks
Use IsOk
and IsErr
when you need to check the state of the Result<T, E>
and don't need to access it's value just quite yet.
Result<DateTime, Error> safeParseResult = SafeParse("2025-01-01");
safeParseResult.IsOk; // true
safeParseResult.IsErr; // false
Consume
You will eventually need to escape from a monadic wrapper in order to access to a concrete value. This is where consume methods come in.
Match
Use Match
to consume the monadic wrapper when you are uncertain of the wrapper's current state. It enables you to pattern match on the outcome and apply branching logic.
Option<string> maybeName = Option.Some("Travis");
int length = maybeName.Match(
name => name.Length,
() => 0
);
Unwrap
Use Unwrap
to consume the monadic wrapper when you are certain the monadic wrapper holds a value, or if you want to fail loudly if it doesn't.
Option<string> maybeName = Option.Some("Lorekeeper");
string name = maybeName.Unwrap();
An UnwrapException
will be thrown when the value is absent or failure.
UnwrapOr
An alternative to Unwrap, use UnwrapOr
to consume the monadic wrapper when you have a fallback value prepared for the None
or Err
states.
Option<string> maybeNickname = Option.None<string>();
string nickname = maybeNickname.UnwrapOr("Lautna");
// ^? "Mate"
UnwrapOrElse
An alternative to UnwrapOr, use UnwrapOrElse
to consume the monadic wrapper when you have a fallback value that requires some expensive computation.
Option<Uri> maybeAvatar = Option.None<Uri>();
Uri avatar = maybeAvatar.UnwrapOrElse(() => GenerateAvatar());
// ^? Generated Avatar
UnwrapOrDefault
An alternative to Unwrap, use UnwrapOrDefault
to consume the monadic wrapper when you are ok with default(T)
as the fallback value.
Option<string> maybeName = Option.None<string>();
string? name = maybeName.UnwrapOrDefault();
// ^? null
Expect
A sibling to Unwrap, but allows you to provide a meaningful error message when an exception is thrown. Use Expect
to consume the monadic wrapper when you expect it to be in a Some
or Ok
state, and you want to fail loudly if it isn't.
Option<string> maybeName = Option.Some("Greymore");
string name = maybeName.Expect("Expected a name, but got nothing.");
An UnmetExpectationException
with your provided message will be thrown when the value is absent or failure.
More Consumes
There are consume methods specific to a Result<T, E>
.
Transform and Consume
MapOr
Use MapOr
when you want to apply a transformation to the contained value, and you have a fallback value ready to go in case the monadic wrapper is a None
or Err
. If your fallback value requires expensive computation, reach for MapOrElse.
Option<string> maybeName = Option.None<string>();
int length = maybeName.MapOr(0, name => name.Length);
// ^? 0
MapOrElse
Use MapOrElse
when you want to apply a transformation to the contained value, consume the monadic wrapper, and you need to perform an expensive calculation to get the fallback value.
Option<User> maybeUser = Option.None<User>();
Uri avatar = maybeUser.MapOrElse(
() => GenerateAvatar(),
user => user.Avatar
);
Side-Effect
Side effects allow you to conditionally access the value when it is Some
or Ok
and run some logic against the value without having to handle the other branch.
Inspect
Use Inspect
when you want to run some logic against the value inside the monadic wrapper when it is in it's Some
or Ok
state without transforming the value inside. The most common use case for Inspect
is to inspect the value inside the wrapper and log it's value.
Option<string> maybeName = Option.Some("Geladon");
maybeName.Inspect(name => Console.WriteLine(name.Length));
More Side-Effects
There are side-effects specific to a Result<T, E>
.
Nesting
You will inevitably find yourself in a situation where you have a monadic wrapper nested inside of a monadic wrapper. Use the below functions to remove a level of nesting at a time.
Flatten
Sometimes you will find yourself with an Option
inside an Option
or a Result
inside of a Result
. Use Flatten
to remove a level of nesting in the monadic wrapper.
Removes one level of nesting from an Option<Option<T>>
Option<Option<string>> some = Option.Some(Option.Some("Chetney"));
Option<string> result = some.Flatten();
Transpose
Sometimes you will find yourself with a Result
inside an Option
or an Option
inside of a Result
. Use Transpose
to convert between them when your business logic needs it.
Result<int, string> Divide(int a, int b);
Option<int> maybeNumber = Option.Try(() => GetNumber());
Option<Result<int, string>> maybeResult = maybeNumber
.Map(number => Divide(number, 2));
Result<Option<int>, string> result = maybeResult.Transpose();
Calling Transpose
in this situation declares that a Result
of None<int>
is a valid outcome in our business rules.
Last updated
Was this helpful?