Maybe<T>
Structure
1public readonly struct Maybe<T>
2{
3 public bool HasValue { get; }
4 public bool HasNoValue { get; }
5 public T Value { get; }
6}
Factory Methods
From
Creates Maybe from nullable value.
1public static Maybe<T> From(T value)
2
3// Example
4string name = GetName(); // might be null
5Maybe<string> maybeName = Maybe<string>.From(name);
None
Creates empty Maybe.
1public static Maybe<T> None { get; }
2
3// Example
4Maybe<User> noUser = Maybe<User>.None;
Implicit Conversion
1// From value
2Maybe<int> maybe = 42;
3
4// Example
5Maybe<string> name = "John";
6Maybe<string> empty = null; // Becomes None
Map
Transforms the value if present.
1public Maybe<TNew> Map<TNew>(Func<T, TNew> mapper)
2
3// Example
4Maybe<string> name = "John";
5Maybe<int> length = name.Map(n => n.Length); // Maybe(4)
Bind
Chains operations that return Maybe.
1public Maybe<TNew> Bind<TNew>(Func<T, Maybe<TNew>> binder)
2
3// Example
4Maybe<User> user = GetUser();
5Maybe<Address> address = user.Bind(u => u.GetPrimaryAddress());
Select (LINQ)
Alias for Map, enables LINQ syntax.
1public Maybe<TNew> Select<TNew>(Func<T, TNew> mapper)
2
3// Example
4var query = from user in maybeUser
5 select user.Name;
SelectMany (LINQ)
Enables LINQ syntax for Bind operations.
1public Maybe<TResult> SelectMany<TNew, TResult>(
2 Func<T, Maybe<TNew>> binder,
3 Func<T, TNew, TResult> projector)
4
5// Example
6var query = from user in maybeUser
7 from profile in user.GetProfile()
8 select new { user.Name, profile.Bio };
Filtering Methods
Where
Filters value based on predicate.
1public Maybe<T> Where(Func<T, bool> predicate)
2
3// Example
4Maybe<int> age = 25;
5Maybe<int> adult = age.Where(a => a >= 18); // Maybe(25)
6Maybe<int> senior = age.Where(a => a >= 65); // None
Side Effect Methods
Tap
Executes action if value exists.
1public Maybe<T> Tap(Action<T> action)
2
3// Example
4maybeUser
5 .Tap(user => Debug.Log($"Found user: {user.Name}"))
6 .Map(user => user.Email);
Execute
Executes action if value exists, returns void.
1public void Execute(Action<T> onValue)
2
3// Example
4maybeConfig.Execute(config => ApplyConfig(config));
ExecuteNoValue
Executes action if no value.
1public void ExecuteNoValue(Action onNoValue)
2
3// Example
4maybeUser.ExecuteNoValue(() => Debug.Log("User not found"));
Pattern Matching
Match
Transforms Maybe to single value.
1public TResult Match<TResult>(
2 Func<T, TResult> onValue,
3 Func<TResult> onNone)
4
5// Example
6string message = maybeUser.Match(
7 onValue: user => $"Hello, {user.Name}",
8 onNone: () => "Hello, Guest"
9);
Match (Async - with NOPE_UNITASK)
Async pattern matching using the same method name.
1public async UniTask<TResult> Match<TResult>(
2 Func<T, UniTask<TResult>> onValue,
3 Func<UniTask<TResult>> onNone)
4
5// Example
6var result = await maybeUser.Match(
7 onValue: async user => await LoadProfileAsync(user),
8 onNone: async () => await CreateGuestProfileAsync()
9);
Or
Provides default value if None.
1public T Or(T defaultValue)
2public T Or(Func<T> defaultFactory)
3public Maybe<T> Or(Maybe<T> alternative)
4
5// Example
6string name = maybeName.Or("Unknown");
7Config config = maybeConfig.Or(() => LoadDefaultConfig());
8Maybe<User> user = primaryUser.Or(secondaryUser);
GetValueOrThrow
Gets value or throws exception.
1public T GetValueOrThrow()
2public T GetValueOrThrow(string errorMessage)
3
4// Example
5var user = maybeUser.GetValueOrThrow("User is required");
GetValueOrDefault
Gets value or default(T).
1public T GetValueOrDefault()
2
3// Example
4int count = maybeCount.GetValueOrDefault(); // 0 if None
TryGetValue
Attempts to get value with out parameter.
1public bool TryGetValue(out T value)
2
3// Example
4if (maybeUser.TryGetValue(out var user))
5{
6 ProcessUser(user);
7}
Conversion Methods
ToResult
Converts Maybe to Result type.
1public static Result<T, E> ToResult<T, E>(
2 this Maybe<T> maybe,
3 E error)
4
5// Example
6Maybe<User> maybeUser = GetUser();
7Result<User, string> result = maybeUser.ToResult("User not found");
8
9// With custom error type
10public enum UserError { NotFound, InvalidId }
11Result<User, UserError> userResult = maybeUser.ToResult(UserError.NotFound);
12
13// Chain with Result operations
14var finalResult = maybeUser
15 .ToResult("User not found")
16 .Bind(user => ValidateUser(user))
17 .Map(user => user.Profile);
Equality and Comparison
Equals
Value equality comparison.
1public bool Equals(Maybe<T> other)
2public override bool Equals(object obj)
3
4// Example
5Maybe<int> a = 42;
6Maybe<int> b = 42;
7bool equal = a.Equals(b); // true
GetHashCode
1public override int GetHashCode()
Operators
1public static bool operator ==(Maybe<T> left, Maybe<T> right)
2public static bool operator !=(Maybe<T> left, Maybe<T> right)
3
4// Example
5if (maybeA == maybeB) { }
Async Extensions (with NOPE_UNITASK)
Map (Async)
Async transformation using the same method name.
1// Sync Maybe → async transform
2public async UniTask<Maybe<TNew>> Map<TNew>(
3 Func<T, UniTask<TNew>> asyncMapper)
4
5// Async Maybe → async transform
6public static async UniTask<Maybe<TNew>> Map<T, TNew>(
7 this UniTask<Maybe<T>> maybeTask,
8 Func<T, UniTask<TNew>> asyncMapper)
9
10// Example
11Maybe<string> url = GetUrl();
12Maybe<Data> data = await url.Map(async u => await FetchAsync(u));
Bind (Async)
Async chaining using the same method name.
1public async UniTask<Maybe<TNew>> Bind<TNew>(
2 Func<T, UniTask<Maybe<TNew>>> asyncBinder)
3
4// Example
5Maybe<User> user = GetUser();
6Maybe<Profile> profile = await user.Bind(
7 async u => await LoadProfileAsync(u.Id)
8);
Tap (Async)
Async side effects using the same method name.
1public async UniTask<Maybe<T>> Tap(Func<T, UniTask> asyncAction)
2
3// Example
4await maybeUser.Tap(async user => await LogUserAccessAsync(user));
Where (Async)
Async filtering using the same method name.
1public async UniTask<Maybe<T>> Where(Func<T, UniTask<bool>> asyncPredicate)
2
3// Example
4Maybe<User> validUser = await maybeUser.Where(
5 async u => await ValidateUserAsync(u)
6);
Collection Extensions
TryFirst
Gets first element as Maybe.
1public static Maybe<T> TryFirst<T>(this IEnumerable<T> source)
2public static Maybe<T> TryFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate)
3
4// Example
5var users = GetUsers();
6Maybe<User> firstAdmin = users.TryFirst(u => u.IsAdmin);
TryLast
Gets last element as Maybe.
1public static Maybe<T> TryLast<T>(this IEnumerable<T> source)
2public static Maybe<T> TryLast<T>(this IEnumerable<T> source, Func<T, bool> predicate)
3
4// Example
5Maybe<LogEntry> lastError = logs.TryLast(l => l.Level == LogLevel.Error);
Note: TrySingle is not yet implemented. See TODO.md for planned features.
TryFind (Dictionary)
Safe dictionary lookup.
1public static Maybe<TValue> TryFind<TKey, TValue>(
2 this IDictionary<TKey, TValue> dictionary,
3 TKey key)
4
5// Example
6var settings = GetSettings();
7Maybe<string> apiUrl = settings.TryFind("ApiUrl");
Note: TryElementAt is not yet implemented. See TODO.md for planned features.
Choose
Filters out None values from collection.
1public static IEnumerable<T> Choose<T>(this IEnumerable<Maybe<T>> source)
2
3// Example
4List<Maybe<User>> maybeUsers = GetMaybeUsers();
5List<User> users = maybeUsers.Choose().ToList(); // Only non-None values
Complete Examples
Example 1: User Profile
1public class UserProfileService
2{
3 public string GetUserDisplayName(string userId)
4 {
5 return FindUser(userId)
6 .Bind(user => user.Profile)
7 .Map(profile => profile.DisplayName)
8 .Where(name => !string.IsNullOrWhiteSpace(name))
9 .Or(() => $"User_{userId}");
10 }
11
12 public Maybe<UserStats> GetUserStats(string userId)
13 {
14 return FindUser(userId)
15 .Bind(user => CalculateStats(user))
16 .Where(stats => stats.IsValid)
17 .Tap(stats => CacheStats(userId, stats));
18 }
19}
Example 2: Configuration
1public class ConfigService
2{
3 private readonly Dictionary<string, string> config;
4
5 public Maybe<int> GetIntConfig(string key)
6 {
7 return config.TryFind(key)
8 .Bind(value => ParseInt(value))
9 .Where(val => val > 0);
10 }
11
12 private Maybe<int> ParseInt(string value)
13 {
14 return int.TryParse(value, out var result)
15 ? Maybe<int>.From(result)
16 : Maybe<int>.None;
17 }
18}
Example 3: LINQ Usage
1public class InventoryService
2{
3 public Maybe<ItemStack> CraftItem(string recipeId, Inventory inventory)
4 {
5 var result =
6 from recipe in FindRecipe(recipeId)
7 where HasIngredients(inventory, recipe)
8 from ingredients in GatherIngredients(inventory, recipe)
9 where ConsumeIngredients(inventory, ingredients)
10 from item in CreateItem(recipe)
11 select new ItemStack(item, recipe.OutputQuantity);
12
13 return result;
14 }
15}
Example 4: Async Operations
1#if NOPE_UNITASK
2public class AsyncUserService
3{
4 public async UniTask<Maybe<UserProfile>> LoadUserProfileAsync(string userId)
5 {
6 return await FindUserIdAsync(userId)
7 .Bind(id => FetchUserFromApiAsync(id))
8 .Where(user => ValidateUserAsync(user))
9 .Map(user => BuildProfileAsync(user))
10 .Tap(profile => CacheProfileAsync(profile));
11 }
12}
13#endif