Skip to main content

Maybe<T> Monad

The Maybe<T> monad is used for modeling operations that can optionally return a value. It provides a safer alternative to null with chainable operations.

Design Goals

The advantage of using Maybe<T> over Nullable<T> is that Maybe<T> provides methods that enable chaining operations in a functional manner.

Practical rule: Use Nullable<T> to model class attributes and Maybe<T> to model return values and method parameters.

Creating Maybe Values

// Some value
var some = Maybe.Some("hello");

// None
var none = Maybe.None<string>();

// From nullable
string? nullable = GetOptionalValue();
var maybe = Maybe.From(nullable);
var maybe2 = (Maybe<string>)nullable; // Implicit conversion

Core Operations

Map

Transforms the value if present:

var maybe = Maybe.Some("hello");
var upper = maybe.Map(s => s.ToUpper()); // Maybe<string> with "HELLO"

var none = Maybe.None<string>();
var result = none.Map(s => s.ToUpper()); // Still None

Bind

Chains operations returning Maybe<T>:

public Maybe<string> ValidateName(string name) =>
!string.IsNullOrWhiteSpace(name)
? Maybe.Some(name.Trim())
: Maybe.None<string>();

var result = Maybe.Some(" John ")
.Bind(ValidateName); // Maybe<string> with "John"

Match

Handles both Some and None cases:

var greeting = maybe.Match(
name => $"Hello, {name}!",
"Hello, stranger!"
);

GetValue

Retrieves value with fallback:

var value = maybe.GetValue("default");
var value2 = maybe.GetValue(() => GetDefaultValue()); // Lazy

Side Effects

maybe
.IfSome(data => Logger.Info($"Got: {data}"))
.IfNone(() => Logger.Warn("No data"));

Converting to Result

var result = maybe
.MapToResult(() => new LogicError("Value not found"))
.Bind(ValidateValue);

See Also