-
Notifications
You must be signed in to change notification settings - Fork 0
Maybe Type
The concepts such as - the "Optional Nomad" and using Option<T> or Maybe<T> instead of primitive nullable types
like int? - are rooted in functional programming paradigms and best practices for writing robust, maintainable code,
and It represents the presence or absence of a value explicitly, avoiding the pitfalls of null.
Here are some disadvantages of Using Nullable:
-
Null Reference Exceptions: Forgetting to check for
nullcan cause runtime crashes. -
Ambiguity:
nullcan signify missing data, errors, or uninitialized values, making code harder to understand. -
Poor Readability: Excessive
nullchecks clutter code and reduce readability. -
Violates Functional Principles:
nullcontradicts functional programming's emphasis on explicitness and immutability.
Here are some advantages of Using Maybe<T>:
- Explicit Handling of Missing Values: Forces developers to handle the absence of a value explicitly.
-
Avoids Magic Numbers: Eliminates the need for special values (e.g.,
-1) to represent missing data which is error-prone and unclear. - Type Safety: Ensures compile-time handling of absent values, reducing runtime errors.
-
Functional Composition: Supports operations like
MapandBind, enabling chaining withoutnullconcerns.
This is a simple example of how to create the Maybe<T> instance:
// Creating a Maybe with a value
Maybe<int> someValue = Maybe.Some(10);
Console.WriteLine(someValue.IsSome); // Output: true
Console.WriteLine(someValue.IsNone); // Output: false
// Creating a Maybe with a value implicitly
Maybe<int> someValue2 = 10;
Console.WriteLine(someValue2.IsSome); // Output: true
Console.WriteLine(someValue2.IsNone); // Output: false
// Creating a Maybe in the 'None' state
Maybe<string> noneValue = Maybe.None<string>();
Console.WriteLine(noneValue.IsSome); // Output: false
Console.WriteLine(noneValue.IsNone); // Output: trueThe value of Maybe<T> instance is not accessible directly, therefore extension methods are provided to help interacting with the Maybe<T> type. This section provides a brief overview of each extension method available in the
ZeidLab.ToolBox.Options namespace. For a list of extension methods that support both synchronous and asynchronous operations, please refer to the Maybe Extension Methods section.
Converts a non-null object to a Maybe<TIn> instance in the 'Some' state.
var maybeInt = 10.ToSome(); // maybeInt is now a Maybe<int> with the 'Some' state.Creates a Maybe<TIn> instance in the None state, regardless of the input value.
var maybeInt = ((int?)null).ToNone(); // maybeInt is now a Maybe<int> with the 'None' state.Checks whether a Maybe<TIn> value both exists and satisfies a condition. Designed as a terminal operation for
validation scenarios.
Key Behavior:
-
Conditional Check: Only evaluates the predicate when the
Maybe<TIn>instance is Some -
Immediate Failure: Returns false without predicate evaluation if the
Maybe<TIn>instance is None - Chain Termination: Cannot be chained further (must be last operation in a sequence)
Maybe<int> maybeInt1 = Maybe.Some(10);
bool result1 = maybeInt1.If(x => x > 5); // result1 is true.
Maybe<int> maybeInt2 = Maybe.None<int>();
bool result2 = maybeInt2.If(x => x > 5); // result2 is false.Notes:
- Think of this as a combination of "has value" and "value meets requirement"
- For pure existence checks, without conditions, use
Maybe<TIn>.IsSomeinstead
whenever there is an instance of IEnumerable<Maybe<TIn>> you can use this method. Filters a collection of Maybe<TIn>
values to include only Maybe<TIn>.IsSome instances that satisfy a specified condition. Automatically excludes
Maybe<TIn>.IsNone values and applies the predicate to the unwrapped Maybe<TIn>.IsSome values.
Key Behavior:
-
None Exclusion: All
Maybe<TIn>.IsNoneentries are omitted from results -
Predicate Filtering: Only
Maybe<TIn>.IsSomevalues matching the condition are retained - LINQ-like Syntax: Designed for parity with standard collection filtering patterns
var maybeList = new List<Maybe<int>>
{
Maybe.Some(5),
Maybe.None<int>(),
Maybe.Some(10)
};
// filtered contains only Maybe.Some(10).
var filtered = maybeList.Where(x => x > 5); Notes:
-
Think of this as combination filter that:
- Removes
Maybe<TIn>.IsNonevalues - Applies your condition to remaining
Maybe<TIn>.IsSomevalues
- Removes
-
Returns
IEnumerable<Maybe<TIn>>containing only qualifyingMaybe<TIn>.IsSomeinstances
Applies a condition to a single Maybe<TIn> instance. Returns the original Maybe<TIn>.IsSome instance if it
satisfies the predicate, otherwise returns Maybe<TIn>.IsNone.
Key Behavior:
-
Single-Value Operation: Works on individual
Maybe<TIn>instances -
State Transition: Converts
Maybe<TIn>.IsSome→Maybe<TIn>.IsNoneif condition fails -
No Unwrapping: Maintains
Maybe<TIn>type throughout
var maybe1 = Maybe.Some(42);
var maybe2 = Maybe.Some(10);
// Returns Maybe.Some(42)
var filtered1 = maybe1.Filter(value => value > 10);
// Returns Maybe.None<int>()
var filtered1 = maybe1.Filter(value => value > 10); | Feature | Filter | Where |
|---|---|---|
| Operates On | SingleMaybe<TIn>
|
IEnumerable<Maybe<TIn>> |
| Return Type | Maybe<TIn> |
IEnumerable<Maybe<TIn>> |
| None Handling | Converts Some → None | Removes None entries |
| Typical Use Case | Conditionally invalidate a value | Filter collections of Maybe's |
Converts a sequence of Maybe<TIn> values into an IEnumerable<TIn>, handling Maybe<TIn>.IsNone cases through three
strategies as three overloads.
Key Characteristics:
-
Type Conversion: Always returns
IEnumerable<TIn>(never containsMaybe<TIn>instances) -
Null Safety: Handles all
Maybe<TIn>.IsNonecases explicitly through exclusion or substitution - Lazy Evaluation: All versions preserve LINQ-like deferred execution
Choosing an Overload:
graph TD
A[Need to handle Nones?] --> |No| B[Use Overload 1]
A --> |Yes| C{Substitution Type?}
C --> |Fixed Value| D[Use Overload 2]
C --> |Dynamic Value| E[Use Overload 3]
Overload 1: Basic Flattening (Excludes Maybe<TIn>.IsNone)
IEnumerable<TIn> Flatten<TIn>(this IEnumerable<Maybe<TIn>> self)-
Behavior: Returns only the values from
Maybe<TIn>.IsSomeinstances, excluding allMaybe<TIn>.IsNoneentries - Use Case: When you want to discard missing values entirely.
Example:
var maybeList = new List<Maybe<int>>
{
Maybe.Some(1),
Maybe.<int>None(),
Maybe.Some(3)
};
var result = maybeList.Flatten(); // Output: [1, 3]Overload 2: Static Substitute Value
IEnumerable<TIn> Flatten<TIn>(this IEnumerable<Maybe<TIn>> self, TIn substitute)-
Behavior: Replaces
Maybe<TIn>.IsNoneentries with a specified default value - Use Case: When missing values should be represented with a known constant
Example:
var maybeList = new List<Maybe<int>>
{
Maybe.Some(1),
Maybe.None<int>(),
Maybe.Some(3)
};
var result = maybeList.Flatten(0); // Output: [1, 0, 3]Overload 3: Dynamic Substitute Generator
IEnumerable<TIn> Flatten<TIn>(this IEnumerable<Maybe<TIn>> self, Func<TIn> substitute)-
Behavior: Uses a function to generate substitutes for
Maybe<TIn>.IsNoneentries - Use Case: When replacement values need dynamic computation (e.g., time-sensitive values)
Example:
var maybeList = new List<Maybe<int>>
{
Maybe.Some(1),
Maybe.None<int>(),
Maybe.Some(3)
};
var result = maybeList.Flatten(() => 0); // Output: [1, 0, 3]Executes a synchronous side effect of Action<TIn> only if the Maybe<TIn> contains a value. Leaves the original
Maybe<TIn> instance unchanged. Do not use this for asynchronous side effects.
Key Behavior:
-
Conditional Execution: Action runs if it is an instance of
Maybe<TIn>.IsSome - No Value Return: Purely for synchronous side effects (synchronous logging,synchronous updates)
-
Chain-Friendly: Returns original
Maybe<TIn>for continued processing
Example:
Maybe<int> maybeInt = Maybe.Some(10);
maybeInt.TapIfSome(x => Console.WriteLine(x)); // Output: 10
// Chaining example
Maybe.Some(5)
.TapIfSome(x => SaveToDatabase(x)) // Side effect
.Bind(x => TransformValue(x)); // Continue processingExecutes a parameterless action only when the instance is of type Maybe<TIn>.IsNone. Designed for handling missing
values.
Key Behavior:
-
Absence Handler: Executes the provided action if the
Maybe<TIn>instance isMaybe<TIn>.IsNone. - No Context Access: Action doesn't receive any value parameter.
- Chain Position: Typically used mid-chain to handle failures.
Maybe<int> maybeNone = Maybe.None<int>();
maybeNone.TapIfNone(() => Console.WriteLine("No value.")); // Output: "No value."
// Practical usage
GetUserInput()
.TapIfNone(() => ShowError("Input required")) // UI feedback
.Filter(IsValidEmail); // Continue validationUnwraps a Maybe<TIn> with no fallback, throwing an exception if the value is missing. Use cautiously.
Key Behavior:
-
Assumed Presence: Throws
InvalidOperationExceptiononMaybe<TIn>.IsNone. -
Last-Resort Unwrap: Prefer
Reduce()for safer alternatives. -
Diagnostic Aid: Useful in contexts where
Maybe<TIn>.IsNoneindicates a code bug.
When to Use:
graph LR
A[Should this code path ever receive `Maybe<TIn>.IsNone`?] --> |No| B[Use `ValueOrThrow`]
A --> |Yes| C[Use `Reduce`]
Example:
Maybe<int> maybeInt = Maybe.Some(10);
int value = maybeInt.ValueOrThrow(); // Returns 10
// Risky usage - avoid without explicit checks
var dangerous = Maybe.None<int>().ValueOrThrow();
// Throws InvalidOperationExceptionThis structure maintains technical precision while emphasizing each method's distinct role:
- TapIfSome/TapIfNone: Side-effect management
- ValueOrThrow: Assertive unwrapping with safety tradeoffs
- Reduce: Unwrapping with fallback options
Inspired by LanguageExt, this library offers a more compact and user-friendly alternative with extensive examples and tutorials.
There is a very detailed YouTube channel with a dedicated video tutorial playlist for this library.
Star this repository and follow me on GitHub to stay informed about new releases and updates. Your support fuels this project's growth!
If my content adds value to your projects, consider supporting me via crypto.
- Bitcoin: bc1qlfljm9mysdtu064z5cf4yq4ddxgdfztgvghw3w
- USDT(TRC20): TJFME9tAnwdnhmqGHDDG5yCs617kyQDV39
Thank you for being part of this community—let’s build smarter, together