Last active
September 20, 2022 01:28
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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