Skip to content

Instantly share code, notes, and snippets.

@bert2
Last active September 20, 2022 01:28
Show Gist options
  • Save bert2/eebc3dbb6c38599a041daaaec16467f8 to your computer and use it in GitHub Desktop.
Save bert2/eebc3dbb6c38599a041daaaec16467f8 to your computer and use it in GitHub Desktop.
copy & paste: lightweight implementation of the famous `Maybe` monad/functor with async support
/**
* Lightweight implementation of the famous `Maybe` type that safely wraps
* optional values without using `null` references.
*
* Add this file to your project and get three new namespaces:
* - `Option`: the base type, factories, operators (map, bind, filter, tap,
* flatten), and means to get the contained value.
* - `Option.Extras`: some utility functions like `IEnumerable.FirstOrNone()`,
* combinators (and, or, ...), and maybe-ready `TryParse` functions.
* - `Option.Async`: async variants of the operators so you can bind against
* `Task`s of maybes, for instance.
*
* It's not a package for naming reasons. Since this type will be used all over
* your project the naming must be perfect. You don't like the name `Option`?
* Go ahead and rename it to `Maybe`, `Optional`, or whatever. Want to align
* the operators with LINQ? Just rename them to `Where()`, `Select()`,
* `SelectMany()` etc.
* Then why not implement it by yourself in the first place? Feel free to do
* that, but it's some annoying busywork, if you want to work with `Task`s of
* maybes.
*
* Documentation: To be honest, I was too lazy to write the XML doc for all the
* functions. I might add them later if this gist gains some
* traction. However, the functions are all rather trivial, so
* you can always just check the implementation to see what's
* going on. Also, an introduction to the concept is available
* here: https://gist.github.com/bert2/2413ea125992fe59d66d24238cf9eba7#make-null-explicit
*
* License: MIT, i.e. do whatever you want with it. Give this gist a star in
* case you like it, please :)
**/
/**
* MIT License
*
* Copyright (c) 2020 Robert Hofmann
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
**/
#nullable enable
namespace Option {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static Option;
public readonly struct Option<T> : IEquatable<Option<T>> where T : notnull {
internal T? Value { get; }
[MemberNotNullWhen(true, nameof(Value))]
public bool IsSome { get; }
[MemberNotNullWhen(false, nameof(Value))]
public bool IsNone => !IsSome;
public Option(T value) {
Value = value ?? throw new ArgumentNullException(nameof(value));
IsSome = true;
}
public static implicit operator Option<T>(T value) => new Option<T>(value);
public override string ToString() => IsSome ? $"Some({Value})" : "None";
#region Equality implementations
public bool Equals(Option<T> other)
=> IsSome == other.IsSome
&& EqualityComparer<T>.Default.Equals(Value, other.Value);
public bool Equals(Option<T>? other) => other.HasValue && Equals(other.Value);
public override bool Equals(object? other) => other is Option<T> opt && Equals(opt);
public static bool operator ==(in Option<T> left, in Option<T> right) => left.Equals(right);
public static bool operator !=(in Option<T> left, in Option<T> right) => !left.Equals(right);
public override int GetHashCode() => HashCode.Combine(IsSome, Value);
#endregion
}
public static class Option {
public static Option<T> Some<T>(T value) where T : notnull => value;
public static Option<T> None<T>() where T : notnull => default;
public static Option<T> FromNullable<T>(T? value) where T : struct => value ?? None<T>();
public static Option<T> FromNullable<T>(T? value) where T : class => value ?? None<T>();
}
public static class OperatorsExt {
public static Option<B> Map<A, B>(this in Option<A> opt, Func<A, B> mapping)
where A : notnull where B : notnull
=> opt.IsSome ? mapping(opt.Value) : None<B>();
public static Option<B> Bind<A, B>(this in Option<A> opt, Func<A, Option<B>> binder)
where A : notnull where B : notnull
=> opt.IsSome ? binder(opt.Value) : None<B>();
public static Option<T> Filter<T>(this in Option<T> opt, Func<T, bool> predicate)
where T : notnull
=> opt.IsSome && predicate(opt.Value) ? opt : None<T>();
public static Option<T> Tap<T>(this in Option<T> opt, Action<T> effect)
where T : notnull {
if (opt.IsSome) effect(opt.Value);
return opt;
}
public static Option<T> Flatten<T>(this in Option<Option<T>> nested)
where T : notnull
=> nested.IsSome ? nested.Value : None<T>();
}
public static class ResolversExt {
public static B Switch<A, B>(this in Option<A> opt, Func<A, B> some, Func<B> none)
where A : notnull
=> opt.IsSome ? some(opt.Value) : none();
public static T GetValueOr<T>(this in Option<T> opt, T fallback)
where T : notnull
=> opt.IsSome ? opt.Value : fallback;
public static T GetValueOr<T>(this in Option<T> opt, Func<T> fallback)
where T : notnull
=> opt.IsSome ? opt.Value : fallback();
public static T GetValueOrThrow<T>(this in Option<T> opt, string? message = null)
where T : notnull
=> opt.IsSome ? opt.Value : throw new InvalidOperationException(message ?? $"{nameof(Option)} did not contain a value.");
}
// Belongs to `ResolversExt`, but both `ToNullable()` functions cannot be in
// the same class, because they're not actually overloads.
public static class ToNullableStructExt {
public static T? ToNullable<T>(this in Option<T> opt)
where T : struct
=> opt.IsSome ? opt.Value : null;
}
public static class ToNullableClassExt {
public static T? ToNullable<T>(this in Option<T> opt)
where T : class
=> opt.IsSome ? opt.Value : null;
}
}
namespace Option.Extras {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using static Option;
public static class UtilExt {
public static Option<V> TryGetValue<K, V>(this IDictionary<K, V> dict, K key)
where V : notnull
=> dict.TryGetValue(key, out var val) ? val : None<V>();
public static Option<T> FirstOrNone<T>(this IEnumerable<T> xs)
where T : notnull
=> xs.Any() ? xs.First() : None<T>();
public static Option<T> SingleOrNone<T>(this IEnumerable<T> xs)
where T : notnull
=> xs.Any() ? xs.Single() : None<T>();
public static Option<int> TryCount<T>(this IEnumerable<T> xs)
=> xs is ICollection<T> c ? c.Count : None<int>();
// Useful to flatten the nested tuples produced when chaining `And()`.
public static (A, B, C) Flatten<A, B, C>(this ((A, B), C) x)
=> (x.Item1.Item1, x.Item1.Item2, x.Item2);
}
public static class CombinatorsExt {
public static Option<T> Or<T>(this in Option<T> opt1, in Option<T> opt2)
where T : notnull
=> opt1.IsSome ? opt1 : opt2;
public static Option<T> Or<T>(this in Option<T> opt1, Func<Option<T>> opt2)
where T : notnull
=> opt1.IsSome ? opt1 : opt2();
public static Option<(A, B)> And<A, B>(this in Option<A> opt1, in Option<B> opt2)
where A : notnull where B : notnull
=> opt1.IsSome && opt2.IsSome ? (opt1.Value, opt2.Value) : None<(A, B)>();
public static Option<A> AndLeft<A, B>(this in Option<A> opt1, in Option<B> opt2)
where A : notnull where B : notnull
=> opt2.IsSome ? opt1 : None<A>();
public static Option<B> AndRight<A, B>(this in Option<A> opt1, in Option<B> opt2)
where A : notnull where B : notnull
=> opt1.IsSome ? opt2 : None<B>();
public static Option<T> FirstSome<T>(this IEnumerable<Option<T>> opts)
where T : notnull
=> opts.Aggregate(None<T>(), (o1, o2) => o1.Or(in o2));
public static IEnumerable<Option<T>> AppendSome<T>(this IEnumerable<Option<T>> opts, in Option<T> opt)
where T : notnull
=> opt.IsSome ? opts.Append(opt) : opts;
public static IEnumerable<Option<T>> PrependSome<T>(this IEnumerable<Option<T>> opts, in Option<T> opt)
where T : notnull
=> opt.IsSome ? opts.Prepend(opt) : opts;
public static IEnumerable<T> ExceptNone<T>(this IEnumerable<Option<T>> opts)
where T : notnull
=> opts.Where(opt => opt.IsSome).Select(opts => opts.Value!);
public static Option<IList<T>> Collect<T>(this IEnumerable<Option<T>> opts)
where T : notnull {
var capacity = opts.TryCount().GetValueOr(10);
var values = new List<T>(capacity);
foreach (var opt in opts) {
if (opt.IsSome)
values.Add(opt.Value);
else
return None<IList<T>>();
}
return values;
}
}
public static class ListIsomorphismExt {
public static Option<T> ToOption<T>(this IEnumerable<T> xs)
where T : notnull
=> xs.Any() ? xs.First() : None<T>();
public static IEnumerable<T> ToEnumerable<T>(this in Option<T> opt)
where T : notnull
=> opt.IsSome ? new[] { opt.Value } : Enumerable.Empty<T>();
}
// generated replacements for `T.TryParse()`
public static class TryParseFunctions {
/// <summary>Tries to parse the input string as a `bool`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<bool> TryParseBool(string value) => bool.TryParse(value, out var r) ? r : None<bool>();
/// <summary>Tries to parse the input string as a `byte`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<byte> TryParseByte(string value) => byte.TryParse(value, out var r) ? r : None<byte>();
/// <summary>Tries to parse the input string as a `byte`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<byte> TryParseByte(string value, NumberStyles style, IFormatProvider provider) => byte.TryParse(value, style, provider, out var r) ? r : None<byte>();
/// <summary>Tries to parse the input string as an `sbyte`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<sbyte> TryParseSByte(string value) => sbyte.TryParse(value, out var r) ? r : None<sbyte>();
/// <summary>Tries to parse the input string as an `sbyte`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<sbyte> TryParseSByte(string value, NumberStyles style, IFormatProvider provider) => sbyte.TryParse(value, style, provider, out var r) ? r : None<sbyte>();
/// <summary>Tries to parse the input string as a `char`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<char> TryParseChar(string value) => char.TryParse(value, out var r) ? r : None<char>();
/// <summary>Tries to parse the input string as a `decimal`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<decimal> TryParseDecimal(string value) => decimal.TryParse(value, out var r) ? r : None<decimal>();
/// <summary>Tries to parse the input string as a `decimal`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<decimal> TryParseDecimal(string value, NumberStyles style, IFormatProvider provider) => decimal.TryParse(value, style, provider, out var r) ? r : None<decimal>();
/// <summary>Tries to parse the input string as a `double`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<double> TryParseDouble(string value) => double.TryParse(value, out var r) ? r : None<double>();
/// <summary>Tries to parse the input string as a `double`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<double> TryParseDouble(string value, NumberStyles style, IFormatProvider provider) => double.TryParse(value, style, provider, out var r) ? r : None<double>();
/// <summary>Tries to parse the input string as a `float`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<float> TryParseFloat(string value) => float.TryParse(value, out var r) ? r : None<float>();
/// <summary>Tries to parse the input string as a `float`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<float> TryParseFloat(string value, NumberStyles style, IFormatProvider provider) => float.TryParse(value, style, provider, out var r) ? r : None<float>();
/// <summary>Tries to parse the input string as an `int`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<int> TryParseInt(string value) => int.TryParse(value, out var r) ? r : None<int>();
/// <summary>Tries to parse the input string as an `int`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<int> TryParseInt(string value, NumberStyles style, IFormatProvider provider) => int.TryParse(value, style, provider, out var r) ? r : None<int>();
/// <summary>Tries to parse the input string as a `uint`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<uint> TryParseUInt(string value) => uint.TryParse(value, out var r) ? r : None<uint>();
/// <summary>Tries to parse the input string as a `uint`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<uint> TryParseUInt(string value, NumberStyles style, IFormatProvider provider) => uint.TryParse(value, style, provider, out var r) ? r : None<uint>();
/// <summary>Tries to parse the input string as a `long`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<long> TryParseLong(string value) => long.TryParse(value, out var r) ? r : None<long>();
/// <summary>Tries to parse the input string as a `long`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<long> TryParseLong(string value, NumberStyles style, IFormatProvider provider) => long.TryParse(value, style, provider, out var r) ? r : None<long>();
/// <summary>Tries to parse the input string as a `ulong`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<ulong> TryParseULong(string value) => ulong.TryParse(value, out var r) ? r : None<ulong>();
/// <summary>Tries to parse the input string as a `ulong`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<ulong> TryParseULong(string value, NumberStyles style, IFormatProvider provider) => ulong.TryParse(value, style, provider, out var r) ? r : None<ulong>();
/// <summary>Tries to parse the input string as a `short`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<short> TryParseShort(string value) => short.TryParse(value, out var r) ? r : None<short>();
/// <summary>Tries to parse the input string as a `short`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<short> TryParseShort(string value, NumberStyles style, IFormatProvider provider) => short.TryParse(value, style, provider, out var r) ? r : None<short>();
/// <summary>Tries to parse the input string as a `ushort`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<ushort> TryParseUShort(string value) => ushort.TryParse(value, out var r) ? r : None<ushort>();
/// <summary>Tries to parse the input string as a `ushort`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<ushort> TryParseUShort(string value, NumberStyles style, IFormatProvider provider) => ushort.TryParse(value, style, provider, out var r) ? r : None<ushort>();
/// <summary>Tries to parse the input string as a `DateTime`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTime> TryParseDateTime(string value) => DateTime.TryParse(value, out var r) ? r : None<DateTime>();
/// <summary>Tries to parse the input string as a `DateTime`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTime> TryParseDateTime(string value, IFormatProvider provider, DateTimeStyles styles) => DateTime.TryParse(value, provider, styles, out var r) ? r : None<DateTime>();
/// <summary>Tries to parse the input string as a `DateTime`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTime> TryParseDateTimeExact(string value, string format, IFormatProvider provider, DateTimeStyles style) => DateTime.TryParseExact(value, format, provider, style, out var r) ? r : None<DateTime>();
/// <summary>Tries to parse the input string as a `DateTime`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTime> TryParseDateTimeExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style) => DateTime.TryParseExact(value, formats, provider, style, out var r) ? r : None<DateTime>();
/// <summary>Tries to parse the input string as a `DateTimeOffset`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTimeOffset> TryParseDateTimeOffset(string value) => DateTimeOffset.TryParse(value, out var r) ? r : None<DateTimeOffset>();
/// <summary>Tries to parse the input string as a `DateTimeOffset`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTimeOffset> TryParseDateTimeOffset(string value, IFormatProvider provider, DateTimeStyles styles) => DateTimeOffset.TryParse(value, provider, styles, out var r) ? r : None<DateTimeOffset>();
/// <summary>Tries to parse the input string as a `DateTimeOffset`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTimeOffset> TryParseDateTimeOffsetExact(string value, string format, IFormatProvider provider, DateTimeStyles style) => DateTimeOffset.TryParseExact(value, format, provider, style, out var r) ? r : None<DateTimeOffset>();
/// <summary>Tries to parse the input string as a `DateTimeOffset`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<DateTimeOffset> TryParseDateTimeOffsetExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style) => DateTimeOffset.TryParseExact(value, formats, provider, style, out var r) ? r : None<DateTimeOffset>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpan(string value) => TimeSpan.TryParse(value, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpan(string value, IFormatProvider provider) => TimeSpan.TryParse(value, provider, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpanExact(string value, string format, IFormatProvider provider) => TimeSpan.TryParseExact(value, format, provider, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpanExact(string value, string format, IFormatProvider provider, TimeSpanStyles style) => TimeSpan.TryParseExact(value, format, provider, style, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpanExact(string value, string[] formats, IFormatProvider provider) => TimeSpan.TryParseExact(value, formats, provider, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `TimeSpan`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<TimeSpan> TryParseTimeSpanExact(string value, string[] formats, IFormatProvider provider, TimeSpanStyles style) => TimeSpan.TryParseExact(value, formats, provider, style, out var r) ? r : None<TimeSpan>();
/// <summary>Tries to parse the input string as a `Guid`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<Guid> TryParseGuid(string value) => Guid.TryParse(value, out var r) ? r : None<Guid>();
/// <summary>Tries to parse the input string as a `Guid`. Returns an `Option` containing the parsed value on success, or an empty `Option` otherwise.</summary>
public static Option<Guid> TryParseGuidExact(string value, string format) => Guid.TryParseExact(value, format, out var r) ? r : None<Guid>();
}
}
namespace Option.Async {
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Extras;
using static Option;
public static class OperatorsSyncToAsyncExt {
public static async Task<Option<B>> MapAsync<A, B>(this Option<A> opt, Func<A, Task<B>> mapping)
where A : notnull where B : notnull
=> opt.IsSome ? await mapping(opt.Value) : None<B>();
public static async Task<Option<B>> BindAsync<A, B>(this Option<A> opt, Func<A, Task<Option<B>>> binder)
where A : notnull where B : notnull
=> opt.IsSome ? await binder(opt.Value) : None<B>();
public static async Task<Option<T>> FilterAsync<T>(this Option<T> opt, Func<T, Task<bool>> predicate)
where T : notnull
=> opt.IsSome && await predicate(opt.Value) ? opt : None<T>();
public static async Task<Option<T>> TapAsync<T>(this Option<T> opt, Func<T, Task> effect)
where T : notnull {
if (opt.IsSome) await effect(opt.Value);
return opt;
}
}
public static class OperatorsAsyncToAsyncExt {
public static async Task<Option<B>> MapAsync<A, B>(this Task<Option<A>> opt, Func<A, Task<B>> mapping)
where A : notnull where B : notnull
=> await (await opt).MapAsync(mapping);
public static async Task<Option<B>> BindAsync<A, B>(this Task<Option<A>> opt, Func<A, Task<Option<B>>> binder)
where A : notnull where B : notnull
=> await (await opt).BindAsync(binder);
public static async Task<Option<T>> FilterAsync<T>(this Task<Option<T>> opt, Func<T, Task<bool>> predicate)
where T : notnull
=> await (await opt).FilterAsync(predicate);
public static async Task<Option<T>> TapAsync<T>(this Task<Option<T>> opt, Func<T, Task> effect)
where T : notnull
=> await (await opt).TapAsync(effect);
}
public static class OperatorsAsyncToSyncExt {
public static async Task<Option<B>> Map<A, B>(this Task<Option<A>> opt, Func<A, B> mapping)
where A : notnull where B : notnull
=> (await opt).Map(mapping);
public static async Task<Option<B>> Bind<A, B>(this Task<Option<A>> opt, Func<A, Option<B>> binder)
where A : notnull where B : notnull
=> (await opt).Bind(binder);
public static async Task<Option<T>> Filter<T>(this Task<Option<T>> opt, Func<T, bool> predicate)
where T : notnull
=> (await opt).Filter(predicate);
public static async Task<Option<T>> Tap<T>(this Task<Option<T>> opt, Action<T> effect)
where T : notnull
=> (await opt).Tap(effect);
}
public static class ResolversSyncToAsyncExt {
public static async Task<B> SwitchAsync<A, B>(this Option<A> opt, Func<A, Task<B>> some, Func<Task<B>> none)
where A : notnull where B : notnull
=> opt.IsSome ? await some(opt.Value) : await none();
public static async Task<T> GetValueOrAsync<T>(this Option<T> opt, Func<Task<T>> fallback)
where T : notnull
=> opt.IsSome ? opt.Value : await fallback();
}
public static class ResolversAsyncToAsyncExt {
public static async Task<B> SwitchAsync<A, B>(this Task<Option<A>> opt, Func<A, Task<B>> some, Func<Task<B>> none)
where A : notnull where B : notnull
=> await (await opt).SwitchAsync(some, none);
public static async Task<T> GetValueOrAsync<T>(this Task<Option<T>> opt, Func<Task<T>> fallback)
where T : notnull
=> await (await opt).GetValueOrAsync(fallback);
}
public static class ResolversAsyncToSyncExt {
public static async Task<B> SwitchAsync<A, B>(this Task<Option<A>> opt, Func<A, B> some, Func<B> none)
where A : notnull where B : notnull
=> (await opt).Switch(some, none);
public static async Task<T> GetValueOr<T>(this Task<Option<T>> opt, T fallback)
where T : notnull
=> (await opt).GetValueOr(fallback);
public static async Task<T> GetValueOr<T>(this Task<Option<T>> opt, Func<T> fallback)
where T : notnull
=> (await opt).GetValueOr(fallback);
public static async Task<T> GetValueOrThrow<T>(this Task<Option<T>> opt, string? message = null)
where T : notnull
=> (await opt).GetValueOrThrow(message);
}
public static class AsyncToNullableStructExt {
public static async Task<T?> ToNullable<T>(this Task<Option<T>> opt)
where T : struct
=> (await opt).ToNullable();
}
public static class AsyncToNullableClassExt {
public static async Task<T?> ToNullable<T>(this Task<Option<T>> opt)
where T : class
=> (await opt).ToNullable();
}
public static class AsyncListIsomorphismExt {
public static async Task<Option<T>> ToOption<T>(this Task<IEnumerable<T>> xs)
where T : notnull
=> (await xs).ToOption();
public static async Task<Option<T>> ToOption<T>(this IAsyncEnumerable<T> xs)
where T : notnull {
await using var e = xs.GetAsyncEnumerator();
return await e.MoveNextAsync() ? e.Current : None<T>();
}
public static async Task<IEnumerable<T>> ToEnumerable<T>(this Task<Option<T>> opt)
where T : notnull
=> (await opt).ToEnumerable();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment