diff --git a/FrameworkFeatureConstants.props b/FrameworkFeatureConstants.props index 6912a6711..b7e712b17 100644 --- a/FrameworkFeatureConstants.props +++ b/FrameworkFeatureConstants.props @@ -21,4 +21,7 @@ $(DefineConstants);REFLECTION_ASSEMBLY_NAME_INFO;REFLECTION_TYPE_NAME;ORDERED_DICTIONARY + + $(DefineConstants);SHUFFLE_EXTENSION;INTEGRATED_ASYNC + diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj b/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj index 069027b95..c7aa8f6e6 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 true true diff --git a/Funcky.Async.Test/Funcky.Async.Test.csproj b/Funcky.Async.Test/Funcky.Async.Test.csproj index d956a47ea..d02c45473 100644 --- a/Funcky.Async.Test/Funcky.Async.Test.csproj +++ b/Funcky.Async.Test/Funcky.Async.Test.csproj @@ -1,4 +1,4 @@ - + net9.0;net8.0;net7.0 preview diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Scan.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Scan.cs index 792d0b69c..ec08a2987 100644 --- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Scan.cs +++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Scan.cs @@ -5,7 +5,7 @@ namespace Funcky.Extensions; public static partial class AsyncEnumerableExtensions { /// - /// Scan generates a sequence known as the the inclusive prefix sum. + /// Scan generates a sequence known as the inclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -17,7 +17,7 @@ public static IAsyncEnumerable InclusiveScan( => InclusiveScanEnumerable(source, seed, accumulator); /// - /// Scan generates a sequence known as the the inclusive prefix sum. + /// Scan generates a sequence known as the inclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -29,7 +29,7 @@ public static IAsyncEnumerable InclusiveScanAwait InclusiveScanAwaitEnumerable(source, seed, accumulator); /// - /// Scan generates a sequence known as the the inclusive prefix sum. + /// Scan generates a sequence known as the inclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -41,7 +41,7 @@ public static IAsyncEnumerable InclusiveScanAwaitWithCancellation InclusiveScanAwaitWithCancellationEnumerable(source, seed, accumulator); /// - /// Scan generates a sequence known as the the exclusive prefix sum. + /// Scan generates a sequence known as the exclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -53,7 +53,7 @@ public static IAsyncEnumerable ExclusiveScan( => ExclusiveScanEnumerable(source, seed, accumulator); /// - /// Scan generates a sequence known as the the exclusive prefix sum. + /// Scan generates a sequence known as the exclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -65,7 +65,7 @@ public static IAsyncEnumerable ExclusiveScanAwait ExclusiveScanAwaitEnumerable(source, seed, accumulator); /// - /// Scan generates a sequence known as the the exclusive prefix sum. + /// Scan generates a sequence known as the exclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. diff --git a/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj b/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj index b50fdf5a8..6eec76836 100644 --- a/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj +++ b/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj @@ -3,7 +3,7 @@ Funcky.SourceGenerator.Test Funcky.SourceGenerator.Test - net9.0 + net10.0 enable enable preview diff --git a/Funcky.Test/AsyncGenerator.cs b/Funcky.Test/AsyncGenerator.cs new file mode 100644 index 000000000..5d5979388 --- /dev/null +++ b/Funcky.Test/AsyncGenerator.cs @@ -0,0 +1,28 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; + +namespace Funcky.Async.Test; + +internal static class AsyncGenerator +{ + public static Arbitrary> GenerateAsyncEnumerable(IArbMap map) + => map.GeneratorFor>().Select(list => list.ToAsyncEnumerable()).ToArbitrary(); + + public static Arbitrary> GenerateAwaitSelector(IArbMap map) + => map.GeneratorFor>().Select(ResultToValueTask).ToArbitrary(); + + public static Arbitrary> GenerateAwaitWithCancellationSelector(IArbMap map) + => map.GeneratorFor>().Select(ResultToValueTaskX).ToArbitrary(); + + private static AwaitSelector ResultToValueTask(Func f) + => new(value => ValueTask.FromResult(f(value))); + + private static AwaitSelectorWithCancellation ResultToValueTaskX(Func f) + => new((value, _) => ValueTask.FromResult(f(value))); +} + +public sealed record AwaitSelector(Func> Get); + +public sealed record AwaitSelectorWithCancellation(Func> Get); +#endif diff --git a/Funcky.Test/AsyncSequence/ConcatTest.cs b/Funcky.Test/AsyncSequence/ConcatTest.cs new file mode 100644 index 000000000..f46884133 --- /dev/null +++ b/Funcky.Test/AsyncSequence/ConcatTest.cs @@ -0,0 +1,40 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class ConcatTest +{ + [Fact] + public async Task ConcatenatedSequenceIsEmptyWhenNoSourcesAreProvidedAsync() + { + await AsyncAssert.Empty(AsyncSequence.Concat()); + } + + [Fact] + public async Task ConcatenatedSequenceIsEmptyWhenAllSourcesAreEmptyAsync() + { + await AsyncAssert.Empty(AsyncSequence.Concat(Enumerable.Empty().ToAsyncEnumerable(), Enumerable.Empty().ToAsyncEnumerable(), Enumerable.Empty().ToAsyncEnumerable())); + } + + [Property] + public Property ConcatenatedSequenceContainsElementsFromAllSourcesInOrder(int[][] sources) + { + var expected = sources.Aggregate(ImmutableArray.Empty, (l, s) => l.AddRange(s)).ToAsyncEnumerable(); + + var innerOuterAsync = sources.Select(source => source.ToAsyncEnumerable()).ToAsyncEnumerable(); + var innerAsync = sources.Select(source => source.ToAsyncEnumerable()); + IAsyncEnumerable> outerAsync = sources.ToAsyncEnumerable(); + + var result = expected.SequenceEqualAsync(AsyncSequence.Concat(innerOuterAsync)).Result + && expected.SequenceEqualAsync(AsyncSequence.Concat(innerAsync)).Result + && expected.SequenceEqualAsync(AsyncSequence.Concat(outerAsync)).Result; + + return result.ToProperty(); + } +} +#endif diff --git a/Funcky.Test/AsyncSequence/CycleRangeTest.cs b/Funcky.Test/AsyncSequence/CycleRangeTest.cs new file mode 100644 index 000000000..407c3e7b1 --- /dev/null +++ b/Funcky.Test/AsyncSequence/CycleRangeTest.cs @@ -0,0 +1,71 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; +using Funcky.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class CycleRangeTest +{ + [Fact] + public async Task CycleRangeIsEnumeratedLazilyAsync() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + await using var cycleRange = AsyncSequence.CycleRange(doNotEnumerate); + } + + [Fact] + public async Task CyclingAnEmptySetThrowsAnArgumentException() + => await Assert.ThrowsAsync(CycleEmptySequenceAsync); + + [Property] + public Property CycleRangeCanProduceArbitraryManyItemsAsync(NonEmptySet sequence, PositiveInt arbitraryElements) + => (GetArbitraryManyItemsAsync(sequence.Get, arbitraryElements.Get).Result == arbitraryElements.Get) + .ToProperty(); + + [Property(Skip = "Tofix")] + public Property CycleRangeRepeatsTheElementsArbitraryManyTimes(NonEmptySet sequence, PositiveInt arbitraryElements) + => CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(sequence.Get.ToAsyncEnumerable(), arbitraryElements.Get) + .Result.ToProperty(); + + [Fact] + public async Task CycleRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync() + { + var sequence = Sequence.Return("Test", "Hello", "Do", "Wait"); + var enumerateOnce = AsyncEnumerateOnce.Create(sequence); + + await using var cycleRange = AsyncSequence.CycleRange(enumerateOnce); + + _ = await cycleRange + .Take(sequence.Count * 3) + .ToListAsync(); + } + + private static async Task GetArbitraryManyItemsAsync(IEnumerable sequence, int arbitraryElements) + { + await using var cycleRange = AsyncSequence.CycleRange(sequence.ToAsyncEnumerable()); + + return await cycleRange.Take(arbitraryElements).CountAsync(); + } + + private static async Task CycleEmptySequenceAsync() + { + await using var cycledRange = AsyncSequence.CycleRange(AsyncSequence.Return()); + await using var enumerator = cycledRange.GetAsyncEnumerator(); + + await enumerator.MoveNextAsync(); + } + + private static async Task CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(IAsyncEnumerable asyncEnumerable, int arbitraryElements) + { + await using var cycleRange = AsyncSequence.CycleRange(asyncEnumerable); + + return await cycleRange + .IsSequenceRepeating(asyncEnumerable) + .NTimes(arbitraryElements); + } +} +#endif diff --git a/Funcky.Test/AsyncSequence/CycleTest.cs b/Funcky.Test/AsyncSequence/CycleTest.cs new file mode 100644 index 000000000..d2f80ddb1 --- /dev/null +++ b/Funcky.Test/AsyncSequence/CycleTest.cs @@ -0,0 +1,25 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class CycleTest +{ + [Property] + public Property CycleCanProduceArbitraryManyItems(int value, PositiveInt arbitraryElements) + => (AsyncSequence.Cycle(value).Take(arbitraryElements.Get).CountAsync().Result == arbitraryElements.Get) + .ToProperty(); + + [Property] + public Property CycleRepeatsTheElementArbitraryManyTimes(int value, PositiveInt arbitraryElements) + => AsyncSequence + .Cycle(value) + .IsSequenceRepeating(AsyncSequence.Return(value)) + .NTimes(arbitraryElements.Get) + .Result + .ToProperty(); +} +#endif diff --git a/Funcky.Test/AsyncSequence/RepeatRangeTest.cs b/Funcky.Test/AsyncSequence/RepeatRangeTest.cs new file mode 100644 index 000000000..88a01e533 --- /dev/null +++ b/Funcky.Test/AsyncSequence/RepeatRangeTest.cs @@ -0,0 +1,101 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; +using Funcky.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class RepeatRangeTest +{ + [Fact] + public async Task RepeatRangeIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + await using var repeatRange = AsyncSequence.RepeatRange(doNotEnumerate, 2); + } + + [Fact] + public async Task RepeatRangeThrowsWhenAlreadyDisposedAsync() + { + var repeatRange = AsyncSequence.RepeatRange(AsyncSequence.Return(1337), 5); + +#pragma warning disable IDISP016 // we test behaviour after Dispose +#pragma warning disable IDISP017 // we test behaviour after Dispose + await repeatRange.DisposeAsync(); +#pragma warning restore IDISP016 +#pragma warning restore IDISP017 + + await Assert.ThrowsAsync(async () => await repeatRange.ToListAsync()); + } + + [Fact] + public async Task RepeatRangeThrowsWhenAlreadyDisposedEvenIfYouDisposeBetweenMoveNextAsync() + { + var list = AsyncSequence.Return(1337, 2, 5); + + const int repeats = 5; + + foreach (var i in Enumerable.Range(0, await list.CountAsync() * repeats)) + { + var repeatRange = AsyncSequence.RepeatRange(list, repeats); + await using var enumerator = repeatRange.GetAsyncEnumerator(); + + Assert.True(await AsyncEnumerable.Range(0, i).AllAsync(async (_, _) => await enumerator.MoveNextAsync())); + +#pragma warning disable IDISP016 // we test behaviour after Dispose +#pragma warning disable IDISP017 // we test behaviour after Dispose + await repeatRange.DisposeAsync(); +#pragma warning restore IDISP016 +#pragma warning restore IDISP017 + + await Assert.ThrowsAnyAsync(async () => await enumerator.MoveNextAsync()); + } + } + + [Property] + public Property TheLengthOfTheGeneratedRepeatRangeIsCorrect(List list, NonNegativeInt count) + => TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(list, count.Get) + .Result + .ToProperty(); + + [Property(Skip = "Tofix")] + public Property TheSequenceRepeatsTheGivenNumberOfTimes(List list, NonNegativeInt count) + => TheSequenceRepeatsTheGivenNumberOfTimesAsync(list.ToAsyncEnumerable(), count.Get) + .Result + .ToProperty(); + + [Fact] + public async Task RepeatRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync() + { + var sequence = Sequence.Return("Test", "Hello", "Do", "Wait"); + var enumerateOnce = AsyncEnumerateOnce.Create(sequence); + + await using var repeatRange = AsyncSequence.RepeatRange(enumerateOnce, 3); + + await foreach (var dummy in repeatRange) + { + } + } + + private static async Task TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(List list, int count) + { + await using var repeatRange = AsyncSequence.RepeatRange(list.ToAsyncEnumerable(), count); + + var materialized = await repeatRange.ToListAsync(); + + return materialized.Count == list.Count * count; + } + + private static async Task TheSequenceRepeatsTheGivenNumberOfTimesAsync(IAsyncEnumerable asyncEnumerable, int count) + { + await using var repeatRange = AsyncSequence.RepeatRange(asyncEnumerable, count); + + return await repeatRange + .IsSequenceRepeating(asyncEnumerable) + .NTimes(count); + } +} +#endif diff --git a/Funcky.Test/AsyncSequence/ReturnTest.cs b/Funcky.Test/AsyncSequence/ReturnTest.cs new file mode 100644 index 000000000..f57bf71c0 --- /dev/null +++ b/Funcky.Test/AsyncSequence/ReturnTest.cs @@ -0,0 +1,35 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class ReturnTest +{ + [Property] + public Property ReturnOfASingleItemElevatesThatItemIntoASingleItemedEnumerable(int item) + { + var sequence = AsyncSequence.Return(item); + + return (sequence.SingleOrNoneAsync().Result == item).ToProperty(); + } + + [Fact] + public async Task SequenceReturnCreatesAnEnumerableFromAnArbitraryNumberOfParameters() + { + const string one = "Alpha"; + const string two = "Beta"; + const string three = "Gamma"; + + var sequence = AsyncSequence.Return(one, two, three); + + await AsyncAssert.Collection( + sequence, + element1 => Assert.Equal(one, element1), + element2 => Assert.Equal(two, element2), + element3 => Assert.Equal(three, element3)); + } +} +#endif diff --git a/Funcky.Test/AsyncSequence/SuccessorsTest.cs b/Funcky.Test/AsyncSequence/SuccessorsTest.cs new file mode 100644 index 000000000..7b3645b63 --- /dev/null +++ b/Funcky.Test/AsyncSequence/SuccessorsTest.cs @@ -0,0 +1,44 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test; + +public sealed class SuccessorsTest +{ + [Fact] + public async Task ReturnsEmptySequenceWhenFirstItemIsNoneAsync() + { + await AsyncAssert.Empty(AsyncSequence.Successors(Option.None, ValueTask.FromResult)); + } + + [Fact] + public async Task ReturnsOnlyTheFirstItemWhenSuccessorFunctionImmediatelyReturnsNoneAsync() + { + var first = await AsyncAssert.Single(AsyncSequence.Successors(10, _ => ValueTask.FromResult(Option.None))); + Assert.Equal(10, first); + } + + [Fact] + public async Task SuccessorsWithNonOptionFunctionReturnsEndlessEnumerableAsync() + { + const int count = 40; + Assert.Equal(count, await AsyncSequence.Successors(0, ValueTask.FromResult).Take(count).CountAsync()); + } + + [Fact] + public async Task SuccessorsReturnsEnumerableThatReturnsValuesBasedOnSeedAsync() + { + await AsyncAssert.Equal( + AsyncEnumerable.Range(0, 10), + AsyncSequence.Successors(0, i => ValueTask.FromResult(i + 1)).Take(10)); + } + + [Fact] + public async Task SuccessorsReturnsEnumerableThatReturnsItemUntilNoneIsReturnedFromFuncAsync() + { + await AsyncAssert.Equal( + AsyncEnumerable.Range(0, 11), + AsyncSequence.Successors(0, i => ValueTask.FromResult(Option.FromBoolean(i < 10, i + 1)))); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/AdjacentGroupByTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AdjacentGroupByTest.cs new file mode 100644 index 000000000..b5ce892b3 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AdjacentGroupByTest.cs @@ -0,0 +1,124 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; +using Funcky.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class AdjacentGroupByTest +{ + private const int DaysInAYear = 365; + private const int DaysInALeapYear = 366; + private const int MonthsInAYear = 12; + private const int February = 1; + private const int DaysInFebruaryInLeapYears = 29; + + [Fact] + public void AdjacentGroupByIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, EqualityComparer.Default); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function, IEnumerable>); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function, EqualityComparer.Default); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function, IEnumerable>, EqualityComparer.Default); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function, FailOnCall.Function, IEnumerable>); + _ = doNotEnumerate.AdjacentGroupBy(FailOnCall.Function, FailOnCall.Function, FailOnCall.Function, IEnumerable>, EqualityComparer.Default); + } + + [Fact] + public async Task GivenAnEmptySequenceAnyKeySelectorReturnsAnEmptySequence() + { + var empty = AsyncEnumerable.Empty(); + + await AsyncAssert.Empty(empty.AdjacentGroupBy(date => date.Month)); + await AsyncAssert.Empty(empty.AdjacentGroupBy(date => date.DayOfYear / 7)); + await AsyncAssert.Empty(empty.AdjacentGroupBy(date => date.Year)); + } + + [Fact] + public async Task GivenConstantKeySelectorOneGroupWithTheConstantsKeyWillBeSelected() + { + const int groupKey = 42; + const int elementCount = 20; + var range = AsyncEnumerable.Range(0, elementCount); + var group = range.AdjacentGroupBy(_ => groupKey); + + var grouping = await AsyncAssert.Single(group); + Assert.Equal(groupKey, grouping.Key); + Assert.Equal(elementCount, grouping.Count()); + } + + [Fact] + public async Task GivenASelectorSwitchingBetween0And1WeGetANewSequenceOnEachSwitch() + { + var range = AsyncEnumerable.Range(0, 100); + + Assert.Equal(25, await range.AdjacentGroupBy(n => (n / 4) % 2).CountAsync()); + } + + [Fact] + public async Task GivenAYearGroupByCreatesMonthsCorrectly() + { + var dates = DateGenerator(2020); + + var months = dates.AdjacentGroupBy(date => date.Month); + + Assert.Equal(DaysInALeapYear, await dates.CountAsync()); + Assert.Equal(MonthsInAYear, await months.CountAsync()); + await AsyncAssert.Equal(DaysInMonthsOfALeapYear(), months.Select((month, _) => month.Count())); + } + + [Fact] + public async Task GivenTwoYearsGroupByAdjacentGroupsJanuaryOfTwoDifferentYearsInTwoDifferentGroups() + { + var dates = DateGenerator(2019, 2020); + + var months = dates.AdjacentGroupBy(date => date.Month); + + Assert.Equal(DaysInAYear + DaysInALeapYear, await dates.CountAsync()); + Assert.Equal(2 * MonthsInAYear, await months.CountAsync()); + await AsyncAssert.Equal(DaysInMonthsOfAYear().Concat(DaysInMonthsOfALeapYear()), months.Select((month, _) => month.Count())); + } + + [Fact] + public async Task GivenAdjacentGroupByWithResultSelectorProjectsTheResultCorrectly() + { + var dates = DateGenerator(2020); + + var months = dates.AdjacentGroupBy(date => date.Month, (_, list) => list.Count()); + + await AsyncAssert.Equal(DaysInMonthsOfALeapYear(), months); + } + + [Fact] + public async Task GivenAdjacentGroupByWithElementSelectorProjectsTheResultCorrectly() + { + var numbers = AsyncEnumerable.Range(1, 5); + + var grouped = numbers.AdjacentGroupBy(number => number / 3, number => number * -1); + Assert.Equal("-3,-4,-5", string.Join(",", await grouped.LastAsync())); + } + +#pragma warning disable CS1998 + private static async IAsyncEnumerable DateGenerator(int startYear, Option endYear = default) +#pragma warning restore CS1998 + { + var current = new DateTime(startYear, 1, 1); + + while (current.Year <= endYear.GetOrElse(startYear)) + { + yield return current; + current = current.AddDays(1); + } + } + + private static IAsyncEnumerable DaysInMonthsOfAYear() + => AsyncSequence.Return(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + + private static IAsyncEnumerable DaysInMonthsOfALeapYear() + => DaysInMonthsOfAYear() + .Select((value, index) => index == February ? DaysInFebruaryInLeapYears : value); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/AnyOrElseTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AnyOrElseTest.cs new file mode 100644 index 000000000..7e737ce8b --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AnyOrElseTest.cs @@ -0,0 +1,44 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class AnyOrElseTest +{ + [Fact] + public void SourceIsEnumeratedLazily() + { + var source = new FailOnEnumerateAsyncSequence(); + _ = source.AnyOrElse(AsyncEnumerable.Empty()); + } + + [Fact] + public void FallbackIsEnumeratedLazily() + { + var source = AsyncEnumerable.Empty().Select(Identity); + _ = source.AnyOrElse(new FailOnEnumerateAsyncSequence()); + } + + [Fact] + public async Task IsEmptyWhenBothEnumerablesAreEmpty() + { + await AsyncAssert.Empty(AsyncEnumerable.Empty().AnyOrElse(AsyncEnumerable.Empty())); + } + + [Fact] + public async Task IsSourceEnumerableWhenNonEmpty() + { + var source = AsyncSequence.Return(1, 2, 3); + var fallback = AsyncSequence.Return(4, 5, 6); + await AsyncAssert.Equal(source, source.AnyOrElse(fallback)); + } + + [Fact] + public async Task IsFallbackEnumerableWhenSourceIsEmpty() + { + var source = AsyncEnumerable.Empty(); + var fallback = AsyncSequence.Return(1, 2, 3); + await AsyncAssert.Equal(fallback, source.AnyOrElse(fallback)); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/AverageOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AverageOrNoneTest.cs new file mode 100644 index 000000000..9815c0986 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/AverageOrNoneTest.cs @@ -0,0 +1,161 @@ +#if INTEGRATED_ASYNC +// ReSharper disable PossibleMultipleEnumeration +using FsCheck; +using FsCheck.Fluent; +using Funcky.Test.Internal; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class AverageOrNoneTest +{ + // Int32/int Tests + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageAsyncForInt32(IAsyncEnumerable sequence) + => CompareAverageAndHandleEmptyInt32SequenceAsync(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageForNullableAsyncForInt32(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt32(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(selector).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select((int? item, CancellationToken _) => selector.Get(item)).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(selector.Get).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Int64/long Tests + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageAsyncForInt64(IAsyncEnumerable sequence) + => CompareAverageAndHandleEmptyInt64SequenceAsync(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageForNullableAsyncForInt64(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt64(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(selector).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select((long? item, CancellationToken _) => selector.Get(item)).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(selector.Get).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Single/float Tests + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageAsyncForSingle(IAsyncEnumerable sequence) + => CompareAverageAndHandleEmptySingleSequenceAsync(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageForNullableAsyncForSingle(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForSingle(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(selector).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select((float? item, CancellationToken _) => selector.Get(item)).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(selector.Get).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Double/double Tests + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageAsyncForDouble(IAsyncEnumerable sequence) + => CompareAverageAndHandleEmptyDoubleSequenceAsync(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageForNullableAsyncForDouble(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDouble(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(selector).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select((double? item, CancellationToken _) => selector.Get(item)).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(selector.Get).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Decimal/decimal Tests + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageAsyncForDecimal(IAsyncEnumerable sequence) + => CompareAverageAndHandleEmptyDecimalSequenceAsync(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncGivesTheSameResultAsAverageForNullableAsyncForDecimal(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDecimal(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(selector).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select((decimal? item, CancellationToken _) => selector.Get(item)).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty] + public Property AverageAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsAverageForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(selector.Get).AverageAsync().Result) + == sequence.Select(Option.FromNullable).AverageOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + private static async Task CompareAverageAndHandleEmptyInt32SequenceAsync(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.AverageAsync() == await sequence.AverageOrNoneAsync() + : (await sequence.AverageOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareAverageAndHandleEmptyInt64SequenceAsync(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.AverageAsync() == await sequence.AverageOrNoneAsync() + : (await sequence.AverageOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareAverageAndHandleEmptySingleSequenceAsync(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.AverageAsync() == await sequence.AverageOrNoneAsync() + : (await sequence.AverageOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareAverageAndHandleEmptyDoubleSequenceAsync(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.AverageAsync() == await sequence.AverageOrNoneAsync() + : (await sequence.AverageOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareAverageAndHandleEmptyDecimalSequenceAsync(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.AverageAsync() == await sequence.AverageOrNoneAsync() + : (await sequence.AverageOrNoneAsync()).Match(none: true, some: _ => false); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/ConcatToStringTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ConcatToStringTest.cs new file mode 100644 index 000000000..2f888768a --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ConcatToStringTest.cs @@ -0,0 +1,46 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public class ConcatToStringTest +{ + [Fact] + public async Task ConcatenatingAnEmptySetOfStringsReturnsAnEmptyString() + { + var empty = AsyncEnumerable.Empty(); + + Assert.Equal(string.Empty, await empty.ConcatToStringAsync()); + } + + [Fact] + public async Task ConcatenatingASetWithExactlyOneElementReturnsTheElement() + { + var singleElement = AsyncSequence.Return("Alpha"); + + Assert.Equal("Alpha", await singleElement.ConcatToStringAsync()); + } + + [Fact] + public async Task ConcatenatingAListOfStringsReturnsAllElementsWithoutASeparator() + { + var strings = AsyncSequence.Return("Alpha", "Beta", "Gamma"); + + Assert.Equal("AlphaBetaGamma", await strings.ConcatToStringAsync()); + } + + [Fact] + public async Task ConcatenatingNonStringsWorksToo() + { + var numbers = AsyncSequence.Return(1, 2, 3); + + Assert.Equal("123", await numbers.ConcatToStringAsync()); + } + + [Fact] + public async Task NullsAreHandledAsEmptyStringsWhileConcatenating() + { + var strings = AsyncSequence.Return("Alpha", null, "Gamma"); + + Assert.Equal("AlphaGamma", await strings.ConcatToStringAsync()); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/ElementAtOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ElementAtOrNoneTest.cs new file mode 100644 index 000000000..f2ddcbd48 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ElementAtOrNoneTest.cs @@ -0,0 +1,43 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using Funcky.FsCheck; +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class ElementAtOrNoneTest +{ + [Theory] + [InlineData(-42)] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(42)] + public async Task ElementAtOrNoneReturnsAlwaysANoneOnAnEmptyEnumerable(int index) + { + FunctionalAssert.None(await EmptyEnumerable.ElementAtOrNoneAsync(index)); + } + + [Fact] + public async Task ElementAtOrNoneReturnsSomeWithinTheRangeAndNoneOutside() + { + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(-10)); + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(-1)); + FunctionalAssert.Some(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(0)); + FunctionalAssert.Some(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(1)); + FunctionalAssert.Some(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(2)); + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(3)); + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(5)); + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.ElementAtOrNoneAsync(10)); + } + + public sealed class IndexIndex + { + [FunckyProperty(Verbose = true)] + public Property BehavesIdenticalToSynchronousCounterpart(List source, Index index) + => (source.ElementAtOrNone(index) == source.ToAsyncEnumerable().ElementAtOrNoneAsync(index).Result) + .ToProperty(); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/FirstOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/FirstOrNoneTest.cs new file mode 100644 index 000000000..6bbbd09ac --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/FirstOrNoneTest.cs @@ -0,0 +1,43 @@ +#if INTEGRATED_ASYNC +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class FirstOrNoneTest +{ + [Fact] + public async Task FirstOrNoneReturnsNoneWhenEnumerableIsEmpty() + { + FunctionalAssert.None(await EmptyEnumerable.FirstOrNoneAsync()); + } + + [Fact] + public async Task FirstOrNoneReturnsItemWhenEnumerableHasOneItem() + { + FunctionalAssert.Some( + FirstItem, + await EnumerableWithOneItem.FirstOrNoneAsync()); + } + + [Fact] + public async Task FirstOrNoneReturnsNoneWhenEnumerableHasOneItemButItDoesNotMatchPredicate() + { + FunctionalAssert.None( + await EnumerableWithOneItem.FirstOrNoneAsync(False)); + } + + [Fact] + public async Task FirstOrNoneReturnsItemWhenEnumerableHasMoreThanOneItem() + { + FunctionalAssert.Some( + FirstItem, + await EnumerableWithMoreThanOneItem.FirstOrNoneAsync()); + } + + [Fact] + public async Task FirstOrNoneReturnsNoneWhenEnumerableHasItemsButNoneMatchesPredicate() + { + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.FirstOrNoneAsync(False)); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectEmptyTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectEmptyTest.cs new file mode 100644 index 000000000..278b3a6af --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectEmptyTest.cs @@ -0,0 +1,37 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class InspectEmptyTest +{ + [Fact] + public void InspectEmptyIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + _ = doNotEnumerate.InspectEmpty(NoOperation); + } + + [Fact] + public async Task InspectEmptyExecutesAnInspectionFunctionOnMaterializationOnAnEmptyEnumerable() + { + var sideEffect = 0; + var asyncEnumerable = AsyncEnumerable.Empty(); + + _ = await asyncEnumerable.InspectEmpty(() => sideEffect = 1).MaterializeAsync(); + + Assert.Equal(1, sideEffect); + } + + [Fact] + public void InspectEmptyExecutesNoInspectionFunctionOnMaterializationOnANonEmptyEnumerable() + { + var sideEffect = 0; + var asyncEnumerable = AsyncSequence.Return("Hello", "World"); + + _ = asyncEnumerable.InspectEmpty(() => sideEffect = 1).MaterializeAsync(); + + Assert.Equal(0, sideEffect); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectTest.cs new file mode 100644 index 000000000..efa8a81a4 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InspectTest.cs @@ -0,0 +1,74 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class InspectTest +{ + [Fact] + public void InspectIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.Inspect(NoOperation); + } + + [Fact] + public void InspectAwaitIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.InspectAwait(static _ => ValueTask.CompletedTask); + } + + [Fact] + public async Task GivenAnAsyncEnumerableAndInjectWeCanApplySideEffectsToAsyncEnumerables() + { + var sideEffect = 0; + var numbers = AsyncSequence.Return(1, 2, 3, 42); + + var numbersWithSideEffect = numbers + .Inspect(n => { ++sideEffect; }); + + Assert.Equal(0, sideEffect); + + await numbersWithSideEffect.ToListAsync(); + + Assert.Equal(await numbers.CountAsync(), sideEffect); + } + + [Fact] + public async Task GivenAnAsyncEnumerableAndInjectAnAsynchronouseActionWeCanApplySideEffectsToAsyncEnumerables() + { + var sideEffect = 0; + var numbers = AsyncSequence.Return(1, 2, 3, 42); + + var numbersWithSideEffect = numbers + .InspectAwait(_ => + { + ++sideEffect; + return default; + }); + + Assert.Equal(0, sideEffect); + + await numbersWithSideEffect.ToListAsync(); + + Assert.Equal(await numbers.CountAsync(), sideEffect); + } + + [Fact] + public async Task CancellationIsPropagated() + { + var canceledToken = new CancellationToken(canceled: true); + _ = await new AssertIsCancellationRequestedAsyncSequence().Inspect(NoOperation).ToListAsync(canceledToken); + } + + [Fact] + public async Task CancellationIsPropagatedInAwaitOverload() + { + var canceledToken = new CancellationToken(canceled: true); + _ = await new AssertIsCancellationRequestedAsyncSequence().InspectAwait(_ => ValueTask.CompletedTask).ToListAsync(canceledToken); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/InterleaveTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InterleaveTest.cs new file mode 100644 index 000000000..9dbbb5ea8 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/InterleaveTest.cs @@ -0,0 +1,101 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class InterleaveTest +{ + [Fact] + public void InterleaveIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.Interleave(); + } + + [Fact] + public async Task GivenAnEmptySequenceOfSequencesInterleaveReturnsAnEmptySequence() + { + var emptySequence = Enumerable.Empty>(); + + var interleaved = emptySequence.Interleave(); + + await AsyncAssert.Empty(interleaved); + } + + [Fact] + public async Task GivenTwoSequencesOfEqualLengthIGetAnInterleavedResult() + { + var odds = AsyncSequence.Return(1, 3, 5, 7, 9, 11); + var evens = AsyncSequence.Return(2, 4, 6, 8, 10, 12); + var expected = AsyncSequence.Return(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + + var interleaved = odds.Interleave(evens); + + await AsyncAssert.Equal(expected, interleaved); + } + + [Fact] + public async Task GivenTwoSequencesOfUnequalLengthIGetAnInterleavedResult() + { + var odds = AsyncSequence.Return(1, 3, 5, 7, 9, 11); + var evens = AsyncSequence.Return(2, 4, 6); + var expected = AsyncSequence.Return(1, 2, 3, 4, 5, 6, 7, 9, 11); + + var interleaved = odds.Interleave(evens); + + await AsyncAssert.Equal(expected, interleaved); + } + + [Theory] + [InlineData("a", "b", "c")] + [InlineData("a", "c", "b")] + [InlineData("b", "a", "c")] + [InlineData("b", "c", "a")] + [InlineData("c", "a", "b")] + [InlineData("c", "b", "a")] + public async Task GivenMultipleSequencesTheOrderIsPreserved(string first, string second, string third) + { + var one = AsyncSequence.Return(first); + var two = AsyncSequence.Return(second); + var three = AsyncSequence.Return(third); + + var interleaved = one.Interleave(two, three); + + await AsyncAssert.Equal(AsyncSequence.Return(first, second, third), interleaved); + } + + [Fact] + public async Task GivenASequenceOfSequenceTheInnerSequencesGetInterleaved() + { + var sequences = Sequence.Return(AsyncEnumerable.Repeat(1, 2), AsyncEnumerable.Repeat(42, 2)); + + await AsyncAssert.Equal(AsyncSequence.Return(1, 42, 1, 42), sequences.Interleave()); + } + + [Fact] + public async Task GivenOneSequenceWithElementsAndAllTheOtherSequencesEmptyWeGetTheFirstSequence() + { + var sequence = AsyncSequence.Return(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + var emptySequence = AsyncEnumerable.Empty(); + + await AsyncAssert.Equal(sequence, emptySequence.Interleave(emptySequence, sequence, emptySequence)); + } + + [Fact] + public async Task GivenASequenceOfSequencesInterleaveReturnsTheExpectedSequence() + { + var sequences = Sequence.Return(AsyncEnumerable.Repeat(1, 10), AsyncEnumerable.Repeat(2, 10), AsyncEnumerable.Repeat(3, 10), AsyncEnumerable.Repeat(4, 10)); + + var innerSum = sequences.Select(async element => await element.CountAsync()).Aggregate(0, (total, part) => total + part.Result); + Assert.Equal(innerSum, await sequences.Interleave().CountAsync()); + + int expected = 1; + await foreach (var element in sequences.Interleave()) + { + Assert.Equal(expected, element); + expected = (expected % 4) + 1; + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/IntersperseTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/IntersperseTest.cs new file mode 100644 index 000000000..67cfd753c --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/IntersperseTest.cs @@ -0,0 +1,43 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class IntersperseTest +{ + [Fact] + public void IntersperseIsEvaluatedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + _ = doNotEnumerate.Intersperse(42); + } + + [Fact] + public async Task InterspersingAnEmptyEnumerableReturnsAnEmptyEnumerable() + { + Assert.False(await AsyncEnumerable.Empty().Intersperse(42).AnyAsync()); + } + + [Fact] + public async Task InterspersingASequenceWithOneElementReturnsOriginalSequence() + { + var source = AsyncSequence.Return(10); + Assert.True(await source.SequenceEqualAsync(source.Intersperse(42))); + } + + [Theory] + [MemberData(nameof(ValueReferenceEnumerables))] + public async Task InterspersingASequenceWithMoreThanOneElementReturnsExpectedSequence(IAsyncEnumerable expected, IAsyncEnumerable source) + { + Assert.True(await expected.SequenceEqualAsync(source.Intersperse(0))); + } + + public static TheoryData, IAsyncEnumerable> ValueReferenceEnumerables() + => new() + { + { AsyncSequence.Return(1, 0, 2), AsyncSequence.Return(1, 2) }, + { AsyncSequence.Return(1, 0, 2, 0, 3), AsyncSequence.Return(1, 2, 3) }, + { AsyncSequence.Return(1, 0, 2, 0, 3, 0, 4), AsyncSequence.Return(1, 2, 3, 4) }, + }; +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/JoinToStringTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/JoinToStringTest.cs new file mode 100644 index 000000000..09a72d344 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/JoinToStringTest.cs @@ -0,0 +1,53 @@ +#if INTEGRATED_ASYNC +// ReSharper disable PossibleMultipleEnumeration + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class JoinToStringTest +{ + [Fact] + public async Task JoiningAnEmptySetOfStringsReturnsAnEmptyString() + { + var empty = AsyncEnumerable.Empty(); + + Assert.Equal(string.Empty, await empty.JoinToStringAsync(", ")); + Assert.Equal(string.Empty, await empty.JoinToStringAsync(',')); + } + + [Fact] + public async Task JoiningASetWithExactlyOneElementReturnsTheElementWithoutASeparator() + { + var singleElement = AsyncSequence.Return("Alpha"); + + Assert.Equal("Alpha", await singleElement.JoinToStringAsync(", ")); + Assert.Equal("Alpha", await singleElement.JoinToStringAsync(',')); + } + + [Fact] + public async Task JoiningAListOfStringsAddsSeparatorsBetweenTheElements() + { + var strings = AsyncSequence.Return("Alpha", "Beta", "Gamma"); + + Assert.Equal("Alpha, Beta, Gamma", await strings.JoinToStringAsync(", ")); + Assert.Equal("Alpha,Beta,Gamma", await strings.JoinToStringAsync(',')); + } + + [Fact] + public async Task JoiningNonStringsReturnASeparatedListToo() + { + var numbers = AsyncSequence.Return(1, 2, 3); + + Assert.Equal("1, 2, 3", await numbers.JoinToStringAsync(", ")); + Assert.Equal("1,2,3", await numbers.JoinToStringAsync(',')); + } + + [Fact] + public async Task NullsAreHandledAsEmptyStringsWhileJoining() + { + var strings = AsyncSequence.Return("Alpha", null, "Gamma"); + + Assert.Equal("Alpha, , Gamma", await strings.JoinToStringAsync(", ")); + Assert.Equal("Alpha,,Gamma", await strings.JoinToStringAsync(',')); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/LastOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/LastOrNoneTest.cs new file mode 100644 index 000000000..aa98db09c --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/LastOrNoneTest.cs @@ -0,0 +1,43 @@ +#if INTEGRATED_ASYNC +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class LastOrNoneTest +{ + [Fact] + public async Task LastOrNoneReturnsNoneWhenEnumerableIsEmpty() + { + FunctionalAssert.None(await EmptyEnumerable.LastOrNoneAsync()); + } + + [Fact] + public async Task LastOrNoneReturnsItemWhenEnumerableHasOneItem() + { + FunctionalAssert.Some( + FirstItem, + await EnumerableWithOneItem.LastOrNoneAsync()); + } + + [Fact] + public async Task LastOrNoneReturnsNoneWhenEnumerableHasOneItemButItDoesNotMatchPredicate() + { + FunctionalAssert.None( + await EnumerableWithOneItem.LastOrNoneAsync(False)); + } + + [Fact] + public async Task LastOrNoneReturnsLastItemWhenEnumerableHasMoreThanOneItem() + { + FunctionalAssert.Some( + LastItem, + await EnumerableWithMoreThanOneItem.LastOrNoneAsync()); + } + + [Fact] + public async Task LastOrNoneReturnsNoneWhenEnumerableHasItemsButNoneMatchesPredicate() + { + FunctionalAssert.None(await EnumerableWithMoreThanOneItem.LastOrNoneAsync(False)); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaterializeTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaterializeTest.cs new file mode 100644 index 000000000..a4b2c086f --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaterializeTest.cs @@ -0,0 +1,36 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; +using Xunit.Sdk; + +namespace Funcky.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class MaterializeTest +{ + [Fact] + public async Task MaterializeEnumeratesNonCollection() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + await Assert.ThrowsAsync(async () => await doNotEnumerate.MaterializeAsync()); + } + + [Fact] + public async Task MaterializeASequenceReturnsAListByDefault() + { + var sequence = AsyncEnumerable.Repeat("Hello world!", 3); + + Assert.IsType>(await sequence.MaterializeAsync()); + } + + [Fact] + public async Task MaterializeWithMaterializationReturnsCorrectCollectionWhenEnumerate() + { + var sequence = AsyncEnumerable.Repeat("Hello world!", 3); + + Assert.IsType>(await sequence.MaterializeAsync(ToHashSet)); + } + + private static ValueTask> ToHashSet(IAsyncEnumerable sequence, CancellationToken cancellationToken) + => sequence.ToHashSetAsync(null, cancellationToken); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaxOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaxOrNoneTest.cs new file mode 100644 index 000000000..e0c4a76be --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MaxOrNoneTest.cs @@ -0,0 +1,222 @@ +#if INTEGRATED_ASYNC +// ReSharper disable PossibleMultipleEnumeration +using FsCheck; +using FsCheck.Fluent; +using Funcky.Test.Internal; +using Funcky.Test.Internal.Data; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class MaxOrNoneTest +{ + // Int32/int Tests + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxAsyncForInt32(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptyInt32Sequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForInt32(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MaxAsync().Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt32(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MaxByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Int64/long Tests + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxAsyncForInt64(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptyInt64Sequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForInt64(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MaxAsync().Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt64(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MaxByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Single/float Tests + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxAsyncForSingle(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptySingleSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForSingle(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MaxAsync().Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForSingle(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MaxByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Double/double Tests + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxAsyncForDouble(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptyDoubleSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForDouble(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MaxAsync().Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDouble(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MaxByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Decimal/decimal Tests + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxAsyncForDecimal(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptyDecimalSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForDecimal(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MaxAsync().Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDecimal(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MaxByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MaxByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Generic TSource implementing IComparable Tests + [FunckyAsyncProperty] + public Property MaxOrNoneGivesTheSameResultAsMaxForAnyIComparable(IAsyncEnumerable sequence) + => CompareMaxAndHandleEmptyPersonSequence(sequence.Select(Person.Create)).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MaxOrNoneAsyncGivesTheSameResultAsMaxForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.Select(Person.Create).MaxAsync().Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MaxOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(Person.Create).MaxByAsync(SelectorTransformation.TransformPersonSelector(selector)).Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MaxOrNoneAsync(SelectorTransformation.TransformOptionPersonSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select(Person.Create).MaxByAsync(SelectorTransformation.TransformPersonSelector(selector.Get)).Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MaxOrNoneAwaitAsync(SelectorTransformation.TransformOptionPersonSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MaxAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMaxForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + { + var result1 = Option.FromNullable(sequence.Select(Person.Create) + .MaxByAsync(SelectorTransformation.TransformPersonSelector(selector.Get)).Result); + + var result2 = sequence.Select(Person.Create).Select(Option.FromNullable) + .MaxOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformOptionPersonSelector(selector.Get)).Result; + + return (result1 == result2).ToProperty(); + } + + [Fact] + public void Failing() + { + var sequence = new List { -1, -1, 1 }; + + var min = Option.FromNullable(sequence.Select(Person.Create).Max()); + var minOrNone = sequence.Select(Person.Create).Select(Option.FromNullable).MaxOrNone(); + + Assert.True(min == minOrNone); + Assert.Equal(0, min.CompareTo(minOrNone)); + Assert.Equal(min, minOrNone); + } + + [Fact] + public void Confused() + { + Person personA = new(42); + Person personB = new(42); + + Assert.Equal(personA, personB); + Assert.Equal(Option.FromNullable(personA), Option.FromNullable(personB)); + } + + private static async Task CompareMaxAndHandleEmptyInt32Sequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MaxAsync() == await sequence.MaxOrNoneAsync() + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMaxAndHandleEmptyInt64Sequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MaxAsync() == await sequence.MaxOrNoneAsync() + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMaxAndHandleEmptySingleSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MaxAsync() == await sequence.MaxOrNoneAsync() + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMaxAndHandleEmptyDoubleSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MaxAsync() == await sequence.MaxOrNoneAsync() + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMaxAndHandleEmptyDecimalSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MaxAsync() == await sequence.MaxOrNoneAsync() + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMaxAndHandleEmptyPersonSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? (await sequence.MaxOrNoneAsync()).Match(none: false, some: p => p.CompareTo(sequence.MaxAsync().Result) == 0) + : (await sequence.MaxOrNoneAsync()).Match(none: true, some: _ => false); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/MemoizeTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MemoizeTest.cs new file mode 100644 index 000000000..7b3fd8115 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MemoizeTest.cs @@ -0,0 +1,111 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class MemoizeTest +{ + [Fact] + public async Task MemoizeIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + await using var memoized = doNotEnumerate.Memoize(); + } + + [Fact] + public async Task TheUnderlyingAsyncEnumerableIsOnlyEnumeratedOnce() + { + var enumerateOnce = AsyncEnumerateOnce.Create(Sequence.Return("Alpha", "Beta")); + await using var memoized = enumerateOnce.Memoize(); + + Assert.Equal("Alpha", await memoized.FirstAsync()); + Assert.Equal("Alpha", await memoized.FirstAsync()); + + Assert.Equal("Beta", await memoized.LastAsync()); + Assert.Equal("Beta", await memoized.LastAsync()); + } + + [Fact] + public async Task MemoizingAnEmptyListIsEmptyAsync() + { + var empty = AsyncEnumerable.Empty(); + await using var memoized = empty.Memoize(); + + await AsyncAssert.Empty(memoized); + } + + [Fact] + public async Task OutOfOrderMoveNextReturnsItemsInTheRightOrderAsync() + { + await using var memoizedRange = AsyncEnumerable.Range(1, 10).Memoize(); + + await using var enumerator1 = memoizedRange.GetAsyncEnumerator(); + + Assert.True(await enumerator1.MoveNextAsync()); + Assert.True(await enumerator1.MoveNextAsync()); + + Assert.Equal(2, enumerator1.Current); + + await using var enumerator2 = memoizedRange.GetAsyncEnumerator(); + + Assert.True(await enumerator2.MoveNextAsync()); + Assert.Equal(1, enumerator2.Current); + + Assert.True(await enumerator1.MoveNextAsync()); + Assert.True(await enumerator1.MoveNextAsync()); + + Assert.Equal(4, enumerator1.Current); + + Assert.True(await enumerator2.MoveNextAsync()); + Assert.Equal(2, enumerator2.Current); + Assert.True(await enumerator2.MoveNextAsync()); + Assert.Equal(3, enumerator2.Current); + Assert.True(await enumerator2.MoveNextAsync()); + Assert.Equal(4, enumerator2.Current); + Assert.True(await enumerator2.MoveNextAsync()); + Assert.Equal(5, enumerator2.Current); + } + + [Fact] + public async Task DisposingAMemoizedBufferDoesNotDisposeOriginalBuffer() + { + var source = AsyncEnumerateOnce.Create(Enumerable.Empty()); + await using var firstMemoization = source.Memoize(); + + await using (firstMemoization.Memoize()) + { + } + + await firstMemoization.ToListAsync(); + } + + [Fact] + public async Task DisposingAMemoizedBorrowedBufferDoesNotDisposeOriginalBorrowedBuffer() + { + var source = AsyncEnumerateOnce.Create([]); + await using var firstMemoization = source.Memoize(); + await using var borrowedBuffer = firstMemoization.Memoize(); + + await using (borrowedBuffer.Memoize()) + { + } + + await borrowedBuffer.ToListAsync(); + } + + /// This test disallows "re-borrowing" i.e. creating a fresh BorrowedBuffer over the original buffer. + [Fact] + public async Task UsagesOfSecondBorrowThrowAfterFirstBorrowIsDisposed() + { + var source = AsyncEnumerateOnce.Create([]); + await using var firstMemoization = source.Memoize(); + await using var firstBorrow = firstMemoization.Memoize(); + await using var secondBorrow = firstBorrow.Memoize(); +#pragma warning disable IDISP017 + await firstBorrow.DisposeAsync(); +#pragma warning restore IDISP017 + await Assert.ThrowsAsync(async () => await secondBorrow.ToListAsync()); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/MergeTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MergeTest.cs new file mode 100644 index 000000000..1daee8d53 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MergeTest.cs @@ -0,0 +1,80 @@ +#if INTEGRATED_ASYNC +// ReSharper disable PossibleMultipleEnumeration + +using System.Collections.Immutable; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; +using Funcky.Test.Internal; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class MergeTest +{ + [Fact] + public void MergeIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.Merge(doNotEnumerate); + } + + [Fact] + public Task MergeEmptySequencesResultsInAnEmptySequence() + { + var emptySequence = AsyncEnumerable.Empty(); + + return AsyncAssert.Empty(emptySequence.Merge(emptySequence, emptySequence, emptySequence)); + } + + [Fact] + public async Task MergeAnEmptySequenceWithANonEmptySequenceResultsInTheNonEmptySequenceAsync() + { + var nonEmptySequence = AsyncSequence.Return(1, 2, 4, 7); + var emptySequence = AsyncEnumerable.Empty(); + + await AsyncAssert.Equal(nonEmptySequence, nonEmptySequence.Merge(emptySequence)); + await AsyncAssert.Equal(nonEmptySequence, emptySequence.Merge(nonEmptySequence)); + } + + [Property] + public void TwoSingleSequencesAreMergedCorrectlyAsync(int first, int second) + { + var sequence1 = AsyncSequence.Return(first); + var sequence2 = AsyncSequence.Return(second); + + var merged = sequence1.Merge(sequence2); + Assert.True(merged.FirstAsync().Result <= merged.LastAsync().Result); + } + + [Fact] + public Task MergeTwoSequencesToOneAsync() + { + var sequence1 = AsyncSequence.Return(1, 2, 4, 7); + var sequence2 = AsyncSequence.Return(3, 5, 6, 8); + var expected = AsyncEnumerable.Range(1, 8); + + return AsyncAssert.Equal(expected, sequence1.Merge(sequence2)); + } + + [Fact] + public Task MergeASequenceOfSequences() + { + var sequence1 = AsyncSequence.Return(1, 2, 4, 7); + var sequence2 = AsyncSequence.Return(3, 5, 6, 8); + var mergeable = ImmutableList>.Empty.Add(sequence1).Add(sequence2); + var expected = AsyncEnumerable.Range(1, 8); + + return AsyncAssert.Equal(expected, mergeable.Merge()); + } + + [Fact] + public Task MergeASequenceWithADifferentComparer() + { + var sequence1 = AsyncSequence.Return(7, 4, 2, 1); + var sequence2 = AsyncSequence.Return(8, 6, 5, 3); + var expected = AsyncEnumerable.Range(1, 8).Reverse(); + + return AsyncAssert.Equal(expected, sequence1.Merge(sequence2, DescendingIntComparer.Create())); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/MinOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MinOrNoneTest.cs new file mode 100644 index 000000000..35400a856 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/MinOrNoneTest.cs @@ -0,0 +1,215 @@ +#if INTEGRATED_ASYNC +// ReSharper disable PossibleMultipleEnumeration +using FsCheck; +using FsCheck.Fluent; +using Funcky.Test.Internal; +using Funcky.Test.Internal.Data; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class MinOrNoneTest +{ + // Int32/int Tests + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinAsyncForInt32(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptyInt32Sequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForInt32(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MinAsync().Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt32(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MinByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt32(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Int64/long Tests + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinAsyncForInt64(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptyInt64Sequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForInt64(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MinAsync().Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt64(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MinByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForInt64(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Single/float Tests + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinAsyncForSingle(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptySingleSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForSingle(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MinAsync().Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForSingle(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MinByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForSingle(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Double/double Tests + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinAsyncForDouble(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptyDoubleSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForDouble(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MinAsync().Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDouble(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MinByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDouble(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Decimal/decimal Tests + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinAsyncForDecimal(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptyDecimalSequence(sequence).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForDecimal(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.MinAsync().Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDecimal(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.MinByAsync(selector).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformNullableSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForDecimal(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.MinByAsync(selector.Get).Result) + == sequence.Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformNullableSelector(selector.Get)).Result).ToProperty(); + + // Generic TSource implementing IComparable Tests + [FunckyAsyncProperty] + public Property MinOrNoneGivesTheSameResultAsMinForAnyIComparable(IAsyncEnumerable sequence) + => CompareMinAndHandleEmptyPersonSequence(sequence.Select(Person.Create)).Result.ToProperty(); + + [FunckyAsyncProperty] + public Property MinOrNoneAsyncGivesTheSameResultAsMinForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence) + => (Option.FromNullable(sequence.Select(Person.Create).MinAsync().Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MinOrNoneAsync().Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, Func selector) + => (Option.FromNullable(sequence.Select(Person.Create).MinByAsync(SelectorTransformation.TransformPersonSelector(selector)).Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MinOrNoneAsync(SelectorTransformation.TransformOptionPersonSelector(selector)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinOrNoneAwaitAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, AwaitSelector selector) + => (Option.FromNullable(sequence.Select(Person.Create).MinByAsync(SelectorTransformation.TransformPersonSelector(selector.Get)).Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MinOrNoneAwaitAsync(SelectorTransformation.TransformOptionPersonSelector(selector.Get)).Result).ToProperty(); + + [FunckyAsyncProperty(Skip = "Tofix")] + public Property MinAwaitWithCancellationAsyncWithSelectorGivesTheSameResultAsMinForNullableAsyncForAnyIComparable(IAsyncEnumerable sequence, AwaitSelectorWithCancellation selector) + => (Option.FromNullable(sequence.Select(Person.Create).MinByAsync(SelectorTransformation.TransformPersonSelector(selector.Get)).Result) + == sequence.Select(Person.Create).Select(Option.FromNullable).MinOrNoneAwaitWithCancellationAsync(SelectorTransformation.TransformOptionPersonSelector(selector.Get)).Result).ToProperty(); + + [Fact] + public void Failing() + { + var sequence = new List { -1, -1, 1 }; + + var min = Option.FromNullable(sequence.Select(Person.Create).Min()); + var minOrNone = sequence.Select(Person.Create).Select(Option.FromNullable).MinOrNone(); + + Assert.True(min == minOrNone); + Assert.Equal(0, min.CompareTo(minOrNone)); + Assert.Equal(min, minOrNone); + } + + [Fact] + public void Confused() + { + Person personA = new(42); + Person personB = new(42); + + Assert.Equal(personA, personB); + Assert.Equal(Option.FromNullable(personA), Option.FromNullable(personB)); + } + + private static async Task CompareMinAndHandleEmptyInt32Sequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MinAsync() == await sequence.MinOrNoneAsync() + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMinAndHandleEmptyInt64Sequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MinAsync() == await sequence.MinOrNoneAsync() + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMinAndHandleEmptySingleSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MinAsync() == await sequence.MinOrNoneAsync() + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMinAndHandleEmptyDoubleSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MinAsync() == await sequence.MinOrNoneAsync() + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMinAndHandleEmptyDecimalSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? await sequence.MinAsync() == await sequence.MinOrNoneAsync() + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); + + private static async Task CompareMinAndHandleEmptyPersonSequence(IAsyncEnumerable sequence) + => await sequence.AnyAsync() + ? (await sequence.MinOrNoneAsync()).Match(none: false, some: p => p.CompareTo(sequence.MinAsync().Result) == 0) + : (await sequence.MinOrNoneAsync()).Match(none: true, some: _ => false); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/PairwiseTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PairwiseTest.cs new file mode 100644 index 000000000..2a9f211c1 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PairwiseTest.cs @@ -0,0 +1,51 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class PairwiseTest +{ + [Fact] + public async Task GivenAnEmptySequencePairwiseReturnsAnEmptySequence() + { + Assert.Empty(await EmptyEnumerable.Pairwise().ToListAsync()); + } + + [Fact] + public async Task GivenASingleElementSequencePairwiseReturnsAnEmptySequence() + { + Assert.Empty(await EnumerableWithOneItem.Pairwise().ToListAsync()); + } + + [Fact] + public async Task GivenATwoElementSequencePairwiseReturnsASequenceWithOneElementTheHeadAndTheTail() + { + var pairwise = await EnumerableTwoItems.Pairwise().ToListAsync(); + + Assert.Single(pairwise, (42, 1337)); + } + + [Fact] + public async Task GivenASequencePairWiseReturnsTheElementsPairwise() + { + const int numberOfElements = 20; + var asyncSequence = Enumerable.Range(0, numberOfElements).ToAsyncEnumerable(); + + var pairs = await asyncSequence.Pairwise().ToListAsync(); + Assert.Equal(numberOfElements - 1, pairs.Count); + + foreach (var (pair, index) in pairs.Select((pair, index) => (pair, index))) + { + Assert.Equal((index, index + 1), pair); + } + } + + [Fact] + public async Task CancellationIsPropagated() + { + var canceledToken = new CancellationToken(canceled: true); + _ = await new AssertIsCancellationRequestedAsyncSequence().Pairwise().ToListAsync(canceledToken); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionEitherTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionEitherTest.cs new file mode 100644 index 000000000..b165859c0 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionEitherTest.cs @@ -0,0 +1,39 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class PartitionEitherTest +{ + [Fact] + public async Task ReturnsTwoEmptyListsWhenSourceIsEmpty() + { + var (left, right) = await AsyncEnumerable.Empty>().PartitionAsync(); + Assert.Empty(left); + Assert.Empty(right); + } + + [Fact] + public async Task PartitionsItemsIntoLeftAndRight() + { + var input = AsyncSequence.Return( + Either.Left(10), + Either.Right("a"), + Either.Right("b"), + Either.Left(20)); + var (left, right) = await input.PartitionAsync(); + Assert.Equal([10, 20], left); + Assert.Equal(["a", "b"], right); + } + + [Fact] + public async Task PartitionsItemsIntoLeftAndRightWithSelector() + { + var input = Enumerable.Range(0, count: 10).Materialize(); + var (left, right) = await input.ToAsyncEnumerable().PartitionAsync(n => IsEven(n) ? Either.Right(n) : Either.Left(n)); + Assert.Equal(input.Where(Not(IsEven)), left); + Assert.Equal(input.Where(IsEven), right); + } + + private static bool IsEven(int n) + => n % 2 is 0; +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionResultTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionResultTest.cs new file mode 100644 index 000000000..eda0e6c8b --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionResultTest.cs @@ -0,0 +1,27 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class PartitionResultTest +{ + [Fact] + public async Task ReturnsTwoEmptyListsWhenSourceIsEmpty() + { + var (error, ok) = await AsyncEnumerable.Empty>().PartitionAsync(); + Assert.Empty(error); + Assert.Empty(ok); + } + + [Fact] + public async Task PartitionsItemsIntoOkAndError() + { + var values = Sequence.Return(10, 20); + var exceptions = Sequence.Return(new Exception("foo"), new InvalidOperationException("bar")); + var input = values.Select(Result.Ok).ToAsyncEnumerable().Interleave(exceptions.Select(Result.Error).ToAsyncEnumerable()); + + var (error, ok) = await input.PartitionAsync(); + + Assert.Equal(values, ok); + Assert.Equal(exceptions, error); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionTest.cs new file mode 100644 index 000000000..9b1af7b5e --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PartitionTest.cs @@ -0,0 +1,56 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class PartitionTest +{ + [Fact] + public async Task ReturnsTwoEmptyListsWhenSourceIsEmpty() + { + var (evens, odds) = await AsyncEnumerable.Empty().PartitionAsync(IsEven); + Assert.Empty(evens); + Assert.Empty(odds); + } + + [Fact] + public async Task PartitionsItemsIntoTruesAndFalses() + { + var (evens, odds) = await AsyncEnumerable.Range(0, 6).PartitionAsync(IsEven); + Assert.Equal([0, 2, 4], evens); + Assert.Equal([1, 3, 5], odds); + } + + [Fact] + public async Task PartitionsItemsIntoTruesAndFalsesWithAsyncPredicate() + { + var (evens, odds) = await AsyncEnumerable.Range(0, 6).PartitionAwaitAsync(x => ValueTask.FromResult(IsEven(x))); + Assert.Equal([0, 2, 4], evens); + Assert.Equal([1, 3, 5], odds); + } + + [Fact] + public async Task PartitionsItemsIntoTruesAndFalsesWithAsyncCancellablePredicate() + { + var (evens, odds) = await AsyncEnumerable.Range(0, 6).PartitionAwaitWithCancellationAsync((x, _) => ValueTask.FromResult(IsEven(x))); + Assert.Equal([0, 2, 4], evens); + Assert.Equal([1, 3, 5], odds); + } + + [Fact] + public async Task RightItemsAreEmptyWhenPredicateMatchesAllItems() + { + var (left, right) = await AsyncEnumerable.Range(0, 6).PartitionAsync(True); + Assert.Equal([0, 1, 2, 3, 4, 5], left); + Assert.Empty(right); + } + + [Fact] + public async Task LeftItemsAreEmptyWhenPredicateMatchesNoItems() + { + var (left, right) = await AsyncEnumerable.Range(0, 6).PartitionAsync(False); + Assert.Empty(left); + Assert.Equal([0, 1, 2, 3, 4, 5], right); + } + + private static bool IsEven(int n) => n % 2 == 0; +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/PowerSetTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PowerSetTest.cs new file mode 100644 index 000000000..0066e7d4f --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/PowerSetTest.cs @@ -0,0 +1,42 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class PowerSetTest +{ + [Fact] + public void APowerSetIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.PowerSet(); + } + + [Fact] + public async Task ThePowerSetOfTheEmptySetIsSetOfTheEmptySet() + { + var powerSet = AsyncEnumerable.Empty().PowerSet(); + + Assert.Empty(await powerSet.FirstAsync()); + } + + [Fact] + public async Task ThePowerSetIsTheSetOfAllSubSets() + { + var sequence = AsyncSequence.Return("Alpha", "Beta", "Gamma"); + var powerSet = sequence.PowerSet(); + + await AsyncAssert.Collection( + powerSet, + subset => { Assert.Equal(Enumerable.Empty(), subset); }, + subset => { Assert.Equal(Sequence.Return("Alpha"), subset); }, + subset => { Assert.Equal(Sequence.Return("Beta"), subset); }, + subset => { Assert.Equal(Sequence.Return("Alpha", "Beta"), subset); }, + subset => { Assert.Equal(Sequence.Return("Gamma"), subset); }, + subset => { Assert.Equal(Sequence.Return("Alpha", "Gamma"), subset); }, + subset => { Assert.Equal(Sequence.Return("Beta", "Gamma"), subset); }, + subset => { Assert.Equal(Sequence.Return("Alpha", "Beta", "Gamma"), subset); }); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/ScanTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ScanTest.cs new file mode 100644 index 000000000..64c4fde0f --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ScanTest.cs @@ -0,0 +1,86 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public class ScanTest +{ + [Fact] + public void InclusiveScanIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.InclusiveScan(0, AddElement); + } + + [Property] + public Property InclusiveScanOnAnEmptyListReturnsAnEmptyList(int neutral, Func func) + { + var empty = AsyncEnumerable.Empty(); + + return empty.InclusiveScan(neutral, func).NoneAsync().Result.ToProperty(); + } + + [Property] + public Property InclusiveScanCalculatesInclusivePrefixSum(int neutralElement, List numbers) + => InclusiveScanCheck(neutralElement, numbers.ToAsyncEnumerable()) + .Result + .ToProperty(); + + [Fact] + public void ExclusiveScanIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.ExclusiveScan(0, AddElement); + } + + [Property] + public Property ExclusiveScanOnAnEmptyListReturnsAnEmptyList(int neutral, Func func) + { + var empty = AsyncEnumerable.Empty(); + + return empty.ExclusiveScan(neutral, func).NoneAsync().Result.ToProperty(); + } + + [Property] + public Property ExclusiveScanCalculatesInclusivePrefixSum(int neutralElement, List numbers) + => ExclusiveScanCheck(neutralElement, numbers.ToAsyncEnumerable()) + .Result + .ToProperty(); + + private static async Task InclusiveScanCheck(int neutralElement, IAsyncEnumerable numbers) + { + var result = true; + var prefixSum = neutralElement; + + await foreach (var (element, inclusiveSum) in numbers.Zip(numbers.InclusiveScan(neutralElement, AddElement))) + { + prefixSum = AddElement(prefixSum, element); + result = result && inclusiveSum == prefixSum; + } + + return result; + } + + private static async Task ExclusiveScanCheck(int neutralElement, IAsyncEnumerable numbers) + { + var result = true; + var prefixSum = neutralElement; + + await foreach (var (element, inclusiveSum) in numbers.Zip(numbers.InclusiveScan(neutralElement, AddElement))) + { + prefixSum = AddElement(prefixSum, element); + result = result && inclusiveSum == prefixSum; + } + + return result; + } + + private static int AddElement(int sum, int element) + => sum + element; +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/ShuffleTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ShuffleTest.cs new file mode 100644 index 000000000..e14ca5e6b --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ShuffleTest.cs @@ -0,0 +1,44 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; +using Xunit.Sdk; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class ShuffleTest +{ + [Fact] + public async Task AShuffleIsEnumeratedLazilyAsync() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + var shuffled = doNotEnumerate.ShuffleAsync(); + + await Assert.ThrowsAsync(async () => await shuffled); + } + + [Fact] + public async Task AShuffleWithASpecificRandomDistributionAlwaysReturnsTheSameShuffle() + { + var source = AsyncEnumerable.Range(0, 16); + + Assert.Equal(Sequence.Return(3, 2, 6, 15, 14, 0, 5, 8, 11, 7, 9, 12, 1, 13, 10, 4), await source.ShuffleAsync(new System.Random(1337))); + } + + [Property] + public Property AShuffleHasTheSameElementsAsTheSource(List source) + => source + .ToAsyncEnumerable() + .ShuffleAsync() + .Result + .All(source.Contains) + .ToProperty(); + + [Property] + public Property AShuffleHasTheSameLengthAsTheSource(List source) + => (source.ToAsyncEnumerable().ShuffleAsync().Result.Count == source.Count) + .ToProperty(); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/SingleOrNoneTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SingleOrNoneTest.cs new file mode 100644 index 000000000..d7f30483d --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SingleOrNoneTest.cs @@ -0,0 +1,75 @@ +#if INTEGRATED_ASYNC +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class SingleOrNoneTest +{ + [Fact] + public async Task SingleOrNoneReturnsNoneWhenEnumerableIsEmpty() + { + FunctionalAssert.None(await EmptyEnumerable.SingleOrNoneAsync()); + } + + [Fact] + public async Task SingleOrNoneReturnsItemWhenEnumerableHasOneItem() + { + FunctionalAssert.Some(FirstItem, await EnumerableWithOneItem.SingleOrNoneAsync()); + } + + [Fact] + public async Task SingleOrNoneThrowsWhenEnumerableContainsMoreThanOneItem() + { + await Assert.ThrowsAsync(async () => + await EnumerableWithMoreThanOneItem.SingleOrNoneAsync()); + } + + [Fact] + public async Task SingleOrNoneReturnsNoneWhenEnumerableHasOneItemAndPredicateDoesNotMatch() + { + FunctionalAssert.None(await EnumerableWithOneItem.SingleOrNoneAsync(False)); + } + + [Fact] + public async Task SingleOrNoneReturnsItemWhenEnumerableHasOneAndPredicateMatches() + { + FunctionalAssert.Some(FirstItem, await EnumerableWithOneItem.SingleOrNoneAsync(True)); + } + + [Fact] + public async Task SingleOrNoneReturnsItemWhenEnumerableContainsMoreThanOneItemButOnlyOneMatchesPredicate() + { + FunctionalAssert.Some(FirstItem, await EnumerableWithMoreThanOneItem.SingleOrNoneAsync(item => item == FirstItem)); + } + + [Fact] + public async Task SingleOrNoneThrowsWhenEnumerableContainsMoreThanOneMatchingItem() + { + await Assert.ThrowsAsync(async () => + await EnumerableWithMoreThanOneItem.SingleOrNoneAsync(True)); + } + + [Fact] + public async Task SingleOrNoneOnlyAdvancesIteratorUpToMatchingItemAndNextBeforeThrowing() + { + const int matchingItem = 3; + const int itemAfterMatchingItem = matchingItem + 1; + await Assert.ThrowsAsync(async () => + await NumbersGreaterThanOrEqualToZero(throwExceptionWhenValueGreaterThan: itemAfterMatchingItem) + .SingleOrNoneAsync(n => n >= matchingItem)); + } + + private static async IAsyncEnumerable NumbersGreaterThanOrEqualToZero(int throwExceptionWhenValueGreaterThan) + { + for (var value = 0; ; value++) + { + if (value > throwExceptionWhenValueGreaterThan) + { + throw new NotSupportedException(); + } + + yield return await Task.FromResult(value); + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs new file mode 100644 index 000000000..bacb0b1d8 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SlidingWindowTest.cs @@ -0,0 +1,91 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class SlidingWindowTest +{ + [Fact] + public void ASlidingWindowIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.SlidingWindow(42); + } + + [Property] + public void ASlidingWindowFromAnEmptySequenceIsAlwaysEmpty(PositiveInt width) + => AsyncEnumerable + .Empty() + .SlidingWindow(width.Get) + .NoneAsync() + .Result + .ToProperty(); + + [Fact] + public async Task GivenASourceSequenceEqualInLengthToTheSlidingWindowWidthReturnsASequenceWithOneElementAsync() + { + var source = AsyncEnumerable.Range(0, 5); + + Assert.Equal(1, await source.SlidingWindow(5).CountAsync()); + Assert.Equal(Enumerable.Range(0, 5), await source.SlidingWindow(5).SingleAsync()); + } + + [Fact] + public async Task GivenASourceSequenceShorterThanTheSlidingWindowWidthReturnsAnEmptySequence() + { + var source = AsyncEnumerable.Range(0, 5); + + await AsyncAssert.Empty(source.SlidingWindow(10)); + } + + [Fact] + public async Task SlidingWindowReturnsTheCorrectAmountOfWindowsAllOfTheSameSizeAsync() + { + const int width = 5; + var source = AsyncEnumerable.Range(0, 10); + var windows = source.SlidingWindow(width); + + Assert.Equal(6, await windows.CountAsync()); + await foreach (var window in windows) + { + Assert.Equal(width, window.Count); + } + } + + [Theory] + [InlineData(int.MinValue)] + [InlineData(-42)] + [InlineData(-1)] + [InlineData(0)] + public void SlidingWindowThrowsOnNonPositiveWidth(int width) + { + var source = AsyncEnumerable.Range(0, 10); + + Assert.Throws(() => source.SlidingWindow(width)); + } + + [Fact] + public async Task SlidingWindowReturnsASequenceOfConsecutiveWindowsAsync() + { + const int width = 4; + var source = AsyncEnumerable.Range(0, 6); + + await AsyncAssert.Collection( + source.SlidingWindow(width), + window => Assert.Equal(Enumerable.Range(0, width), window), + window => Assert.Equal(Enumerable.Range(1, width), window), + window => Assert.Equal(Enumerable.Range(2, width), window)); + } + + [Fact] + public async Task CancellationIsPropagated() + { + var canceledToken = new CancellationToken(canceled: true); + _ = await new AssertIsCancellationRequestedAsyncSequence().SlidingWindow(1).ToListAsync(canceledToken); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/SplitTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SplitTest.cs new file mode 100644 index 000000000..06e4910c7 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/SplitTest.cs @@ -0,0 +1,42 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class SplitTest +{ + [Fact] + public void SplitIsAnIAsyncEnumerableLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.Split(42); + } + + [Property] + public Property SplittingAnEmptyIAsyncEnumerableAlwaysReturnsAnEmptyEnumerable(int separator) + { + var parts = AsyncEnumerable.Empty().Split(separator); + + return (!parts.AnyAsync().Result).ToProperty(); + } + + [Fact] + public async Task SplitAnIAsyncEnumerableCorrectly() + { + var sequence = AsyncSequence.Return(12, 14, 7, 41, 31, 19, 7, 9, 11, 99, 99); + + var parts = sequence.Split(7); + + var expected = AsyncSequence.Return( + Sequence.Return(12, 14), + Sequence.Return(41, 31, 19), + Sequence.Return(9, 11, 99, 99)); + + await AsyncAssert.Equal(expected, parts); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/TakeEveryTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TakeEveryTest.cs new file mode 100644 index 000000000..f5bb3308b --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TakeEveryTest.cs @@ -0,0 +1,51 @@ +#if INTEGRATED_ASYNC +using static Funcky.Async.Test.Extensions.AsyncEnumerableExtensions.TestData; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class TakeEveryTest +{ + [Fact] + public async Task TakeEveryOnAnEmptySequenceReturnsAnEmptySequence() + { + Assert.Empty(await EmptyEnumerable.TakeEvery(5).ToListAsync()); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(-42)] + public async Task TakeEveryOnlyAcceptsPositiveIntervals(int negativeIntervals) + { + await Assert.ThrowsAsync(async () => await EnumerableWithOneItem.TakeEvery(negativeIntervals).ToListAsync()); + } + + [Fact] + public async Task TakeEverySelectsEveryNthElement() + { + var numbers = AsyncEnumerable.Range(-60, 120); + + Assert.Equal(await numbers.CountAsync() / 6, await numbers.TakeEvery(6).CountAsync()); + + await foreach (var n in numbers.TakeEvery(6)) + { + Assert.True(n % 6 == 0); + } + } + + [Fact] + public async Task TakeEveryWithLessThanIntervalElementsReturnsOnlyFirstElement() + { + var everyThird = await EnumerableWithMoreThanOneItem.TakeEvery(3).ToListAsync(); + + Assert.Single(everyThird); + Assert.Equal("first", everyThird.Single()); + } + + [Fact] + public async Task TakeEveryWithASourceWith5ElementsAndInterval4Returns2Elements() + { + Assert.Equal([1, 5], await OneToFive.TakeEvery(4).ToListAsync()); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/TestData.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TestData.cs new file mode 100644 index 000000000..7f3b891d5 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TestData.cs @@ -0,0 +1,27 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +internal static class TestData +{ + public const string FirstItem = "first"; + + public const string LastItem = "last"; + + public const string MiddleItem = "middle"; + + public static readonly IAsyncEnumerable EmptyEnumerable + = AsyncEnumerable.Empty(); + + public static readonly IAsyncEnumerable EnumerableWithOneItem + = AsyncSequence.Return(FirstItem); + + public static readonly IAsyncEnumerable EnumerableTwoItems + = AsyncSequence.Return(42, 1337); + + public static readonly IAsyncEnumerable EnumerableWithMoreThanOneItem + = AsyncSequence.Return(FirstItem, MiddleItem, LastItem); + + public static readonly IAsyncEnumerable OneToFive + = AsyncSequence.Return(1, 2, 3, 4, 5); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/TransposeTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TransposeTest.cs new file mode 100644 index 000000000..b1de4ff08 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/TransposeTest.cs @@ -0,0 +1,112 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; +using Funcky.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class TransposeTest +{ + [Fact] + public async Task TransposeIsLazyElementsGetOnlyEnumeratedWhenRequested() + { + const int numberOfRows = 5; + const int numberOfColumns = 3; + var lazyMatrix = LazyMatrix(numberOfRows, numberOfColumns); + + var transposedMatrix = lazyMatrix.Transpose(); + + Assert.Equal(0, CountCreation.Count); + + await foreach (var row in transposedMatrix) + { + _ = row.ToList(); + } + + Assert.Equal(numberOfRows * numberOfColumns, CountCreation.Count); + } + + [Fact] + public async Task TransposingAnEmptyMatrixResultsInAnEmptyMatrix() + { + var emptyMatrix = Enumerable.Empty>(); + + var transposedMatrix = emptyMatrix.Transpose(); + + await AsyncAssert.Empty(transposedMatrix); + } + + [Fact] + public async Task TransposingAMatrixResultsInATransposedMatrixAsync() + { + var transposed = MatrixExample().Transpose(); + + await AsyncAssert.Collection( + transposed, + row => { Assert.Equal([1, 5, 9], row); }, + row => { Assert.Equal([2, 6, 10], row); }, + row => { Assert.Equal([3, 7, 11], row); }, + row => { Assert.Equal([4, 8, 12], row); }); + } + + [Fact] + public async Task GivenAMagicSquareTransposeDoesNotChangeTheAverages() + { + MagicSquare() + .Select(x => x.AverageAsync()) + .ForEach(async average => Assert.Equal(5, await average)); + + var averages = MagicSquare() + .Transpose() + .Select(Enumerable.Average); + + await foreach (var average in averages) + { + Assert.Equal(5, average); + } + } + + [Fact] + public async Task GivenAJaggedArrayTheTransposeDoesNotWorkAsExpected() + { + // Jagged sequences do not work! + // If you use jagged sequences, in Transpose you are using an implementation detail which could change. + var transposed = JaggedMatrixExample().Transpose(); + + await AsyncAssert.Collection( + transposed, + row => { Assert.Equal([1, 6, 5, 10], row); }, + row => { Assert.Equal([2, 9, 3, 42], row); }, + row => { Assert.Equal([4], row); }); + } + + private static IEnumerable> MagicSquare() + => + [ + AsyncSequence.Return(4, 9, 2), + AsyncSequence.Return(3, 5, 7), + AsyncSequence.Return(8, 1, 6), + ]; + + private static IEnumerable> MatrixExample() + => + [ + AsyncSequence.Return(1, 2, 3, 4), + AsyncSequence.Return(5, 6, 7, 8), + AsyncSequence.Return(9, 10, 11, 12) + ]; + + private static IEnumerable> JaggedMatrixExample() + => + [ + AsyncSequence.Return(1, 2, 3, 4), + AsyncSequence.Return(6, 9, 42), + AsyncSequence.Return(5), + AsyncSequence.Return(10) + ]; + + private static IEnumerable> LazyMatrix(int rows, int columns) => + from row in Enumerable.Range(0, rows) + select from column in AsyncEnumerable.Range(0, columns) + select new CountCreation(); +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereNotNullTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereNotNullTest.cs new file mode 100644 index 000000000..a75465495 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereNotNullTest.cs @@ -0,0 +1,34 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WhereNotNullTest +{ + [Fact] + public void WhereNotNullIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.WhereNotNull(); + } + + [Fact] + public Task WhereNotNullRemovesNullReferenceValues() + { + var input = AsyncSequence.Return(null, "foo", null, "bar", null); + var expectedResult = AsyncSequence.Return("foo", "bar"); + + return AsyncAssert.Equal(expectedResult, input.WhereNotNull()); + } + + [Fact] + public Task WhereNotNullRemovesNullValueTypeValues() + { + var input = AsyncSequence.Return(null, 10, null, 20, null); + var expectedResult = AsyncSequence.Return(10, 20); + + return AsyncAssert.Equal(expectedResult, input.WhereNotNull()); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereSelectTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereSelectTest.cs new file mode 100644 index 000000000..1cb20ac99 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WhereSelectTest.cs @@ -0,0 +1,39 @@ +#if INTEGRATED_ASYNC +using static Funcky.Discard; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WhereSelectTest +{ + [Fact] + public async Task WhereSelectRetainsOnlySomeValues() + { + var expectedSquares = new[] { 0, 4, 16, 36, 64 }; + var squares = await Ι(10).WhereSelect(SquareEvenNumbers).ToListAsync(); + Assert.Equal(expectedSquares, squares); + } + + [Fact] + public async Task WhereSelectReceivesTheSourceElementsIndex() + { + const int count = 6; + var expectedSequence = Enumerable.Range(0, count: count); + var units = AsyncSequence.Cycle(__).Take(count); + var indexes = units.WhereSelect((_, index) => Option.Some(index)); + Assert.Equal(expectedSequence, await indexes.ToListAsync()); + } + + private static Option SquareEvenNumbers(int n) + => n % 2 == 0 + ? n * n + : Option.None; + + private static async IAsyncEnumerable Ι(int n) + { + for (var ι = 0; ι < n; ι++) + { + yield return await new ValueTask(ι); + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithFirstTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithFirstTest.cs new file mode 100644 index 000000000..787c109c4 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithFirstTest.cs @@ -0,0 +1,53 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WithFirstTest +{ + [Fact] + public void WithFirstIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.WithFirst(); + } + + [Fact] + public async Task AnEmptySequenceWithFirstReturnsAnEmptySequence() + { + var emptySequence = AsyncEnumerable.Empty(); + + await AsyncAssert.Empty(emptySequence.WithIndex()); + } + + [Fact] + public async Task ASequenceWithOneElementWithFirstHasOneElementWhichIsMarkedFirst() + { + const string expectedValue = "Hello world!"; + var oneElementSequence = AsyncSequence.Return(expectedValue); + + var sequenceWithFirst = oneElementSequence.WithFirst(); + await foreach (var (value, isFirst) in sequenceWithFirst) + { + Assert.Equal(expectedValue, value); + Assert.True(isFirst); + } + + await AsyncAssert.NotEmpty(sequenceWithFirst); + } + + [Fact] + public async Task ASequenceWithMultipleElementsWithFirstMarksTheFirstElement() + { + const int length = 20; + + var sequence = AsyncEnumerable.Range(1, length); + + await foreach (var valueWithFirst in sequence.WithFirst()) + { + Assert.Equal(valueWithFirst.Value == 1, valueWithFirst.IsFirst); + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithIndexTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithIndexTest.cs new file mode 100644 index 000000000..42ed9c8f9 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithIndexTest.cs @@ -0,0 +1,51 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WithIndexTest +{ + [Fact] + public void WithIndexIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.WithIndex(); + } + + [Fact] + public async Task AnEmptySequenceWithIndexReturnsAnEmptySequence() + { + var emptySequence = AsyncEnumerable.Empty(); + + await AsyncAssert.Empty(emptySequence.WithIndex()); + } + + [Fact] + public async Task ASequenceWithOneElementWithIndexHasTheIndexZero() + { + const string expectedValue = "Hello world!"; + var oneElementSequence = AsyncSequence.Return(expectedValue); + + var sequenceWithIndex = oneElementSequence.WithIndex(); + await foreach (var (value, index) in sequenceWithIndex) + { + Assert.Equal(expectedValue, value); + Assert.Equal(0, index); + } + + await AsyncAssert.NotEmpty(sequenceWithIndex); + } + + [Fact] + public async Task ASequenceWithMultipleElementsWithIndexHaveAscendingIndices() + { + var sequence = AsyncEnumerable.Range(0, 20); + + await foreach (var valueWithIndex in sequence.WithIndex()) + { + Assert.Equal(valueWithIndex.Value, valueWithIndex.Index); + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithLastTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithLastTest.cs new file mode 100644 index 000000000..d9a715bfe --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithLastTest.cs @@ -0,0 +1,52 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WithLastTest +{ + [Fact] + public void WithLastIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.WithLast(); + } + + [Fact] + public async Task AnEmptySequenceWithLastReturnsAnEmptySequence() + { + var emptySequence = AsyncEnumerable.Empty(); + + await AsyncAssert.Empty(emptySequence.WithIndex()); + } + + [Fact] + public async Task ASequenceWithOneElementWithFirstHasOneElementWhichIsMarkedLast() + { + const string expectedValue = "Hello world!"; + var oneElementSequence = AsyncSequence.Return(expectedValue); + + var sequenceWithLast = oneElementSequence.WithLast(); + await foreach (var (value, isLast) in sequenceWithLast) + { + Assert.Equal(expectedValue, value); + Assert.True(isLast); + } + + await AsyncAssert.NotEmpty(sequenceWithLast); + } + + [Fact] + public async Task ASequenceWithMultipleElementsWithLastMarksTheLastElement() + { + const int length = 20; + var sequence = AsyncEnumerable.Range(1, length); + + await foreach (var (value, isLast) in sequence.WithLast()) + { + Assert.Equal(value == length, isLast); + } + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithPreviousTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithPreviousTest.cs new file mode 100644 index 000000000..a0e5445af --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/WithPreviousTest.cs @@ -0,0 +1,49 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class WithPreviousTest +{ + [Fact] + public void WithPreviousIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.WithPrevious(); + } + + [Fact] + public async Task AnEmptySequenceWithPreviousReturnsAnEmptySequence() + { + var emptySequence = AsyncEnumerable.Empty(); + await AsyncAssert.Empty(emptySequence.WithPrevious()); + } + + [Fact] + public async Task ASequenceWithOneElementWithPreviousHasOneElementWithNoPreviousElement() + { + const string expectedValue = "Hello world!"; + var oneElementSequence = AsyncSequence.Return(expectedValue); + var sequenceWithPrevious = oneElementSequence.WithPrevious(); + await AsyncAssert.Collection(sequenceWithPrevious, value => + { + Assert.Equal(expectedValue, value.Value); + FunctionalAssert.None(value.Previous); + }); + } + + [Fact] + public async Task ASequenceWithMoreThanOneElementWithPreviousHasPreviousSetExceptOnFirstElement() + { + var sequence = AsyncSequence.Return("foo", "bar", "baz", "qux"); + var expectedSequenceWithPrevious = AsyncSequence.Return( + new ValueWithPrevious("foo", Option.None), + new ValueWithPrevious("bar", "foo"), + new ValueWithPrevious("baz", "bar"), + new ValueWithPrevious("qux", "baz")); + + Assert.Equal(await expectedSequenceWithPrevious.ToListAsync(), await sequence.WithPrevious().ToListAsync()); + } +} +#endif diff --git a/Funcky.Test/Extensions/AsyncEnumerableExtensions/ZipLongestTest.cs b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ZipLongestTest.cs new file mode 100644 index 000000000..f9decd5e2 --- /dev/null +++ b/Funcky.Test/Extensions/AsyncEnumerableExtensions/ZipLongestTest.cs @@ -0,0 +1,65 @@ +#if INTEGRATED_ASYNC +using Funcky.Async.Test.TestUtilities; + +namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; + +public sealed class ZipLongestTest +{ + [Fact] + public void ZipLongestIsEnumeratedLazily() + { + var doNotEnumerate = new FailOnEnumerateAsyncSequence(); + + _ = doNotEnumerate.ZipLongest(doNotEnumerate, _ => Unit.Value); + } + + [Fact] + public async Task GivenTwoEmptySequencesZipLongestReturnsAnEmptySequence() + { + var numbers = AsyncEnumerable.Empty(); + var strings = AsyncEnumerable.Empty(); + + var zipped = numbers.ZipLongest(strings, _ => Unit.Value); + + await AsyncAssert.Empty(zipped); + } + + [Fact] + public async Task GivenTwoSequencesOfTheSameLengthWeGetNoNoneValue() + { + var numbers = AsyncEnumerable.Range(0, 3); + var strings = AsyncSequence.Return("Alpha", "Beta", "Gamma"); + + var zipped = numbers + .ZipLongest(strings); + + Assert.Equal(3, await zipped.CountAsync()); + await foreach (var value in zipped) + { + Assert.True(value.Match(left: False, right: False, both: True)); + } + } + + [Fact] + public async Task GivenTwoSequencesWeOfDifferentLengthWeGetTheLongerAndFillWithNone() + { + var numbers = AsyncEnumerable.Range(0, 10); + var strings = AsyncSequence.Return("Alpha", "Beta", "Gamma"); + + var zipped = numbers + .ZipLongest(strings); + + Assert.Equal(10, await zipped.CountAsync()); + + Assert.True((await zipped.FirstAsync()).Match( + left: False, + right: False, + both: (left, right) => left == 0 && right == "Alpha")); + + Assert.True((await zipped.LastAsync()).Match( + left: left => left == 9, + right: False, + both: False)); + } +} +#endif diff --git a/Funcky.Test/Extensions/EnumerableExtensions/AdjacentGroupByTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/AdjacentGroupByTest.cs index 273ad3648..8645be2c5 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/AdjacentGroupByTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/AdjacentGroupByTest.cs @@ -1,5 +1,5 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/AnyOrElseTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/AnyOrElseTest.cs index 53d1c71af..8f94b581c 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/AnyOrElseTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/AnyOrElseTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs index 90f273cc0..a91da1158 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/ChunkTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs index ce90a8ab1..c3896e824 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs @@ -1,6 +1,6 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing using System.Collections; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using Xunit.Sdk; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ForEachTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ForEachTest.cs index e6cd40b7a..71a0e3dfc 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/ForEachTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/ForEachTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using Xunit.Sdk; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/GetNonEnumeratedCountOrNoneTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/GetNonEnumeratedCountOrNoneTest.cs index c0526aa12..8cfcefa43 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/GetNonEnumeratedCountOrNoneTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/GetNonEnumeratedCountOrNoneTest.cs @@ -2,7 +2,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/InspectEmptyTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/InspectEmptyTest.cs index 4087fcfe9..57dfe60fd 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/InspectEmptyTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/InspectEmptyTest.cs @@ -1,5 +1,5 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/InspectTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/InspectTest.cs index 199041d6d..b3bde480d 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/InspectTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/InspectTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/InterleaveTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/InterleaveTest.cs index 1a75f00bb..65944372a 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/InterleaveTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/InterleaveTest.cs @@ -1,5 +1,5 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/IntersperseTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/IntersperseTest.cs index 5477e79a9..6cbfb5330 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/IntersperseTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/IntersperseTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/MaterializeTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/MaterializeTest.cs index 8976ed8ca..8cf3e3a69 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/MaterializeTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/MaterializeTest.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using Xunit.Sdk; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/MemoizeTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/MemoizeTest.cs index e70ccd34b..a959a3eea 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/MemoizeTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/MemoizeTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/MergeTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/MergeTest.cs index bc3a0e7f7..f2ab62d62 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/MergeTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/MergeTest.cs @@ -5,7 +5,7 @@ using FsCheck.Fluent; using FsCheck.Xunit; using Funcky.Test.Internal; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/PairwiseTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/PairwiseTest.cs index 6f3f46c6a..1524d8350 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/PairwiseTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/PairwiseTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/PowerSetTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/PowerSetTest.cs index 0065df8bb..b8de245a6 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/PowerSetTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/PowerSetTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ScanTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ScanTest.cs index 0d2201806..136f2482b 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/ScanTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/ScanTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.AsyncEnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ShuffleTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ShuffleTest.cs index 43f96f469..0c5ce9eb6 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/ShuffleTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/ShuffleTest.cs @@ -1,13 +1,16 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +#if !SHUFFLE_EXTENSION +using Funcky.Test.TestUtilities; using Xunit.Sdk; +#endif namespace Funcky.Test.Extensions.EnumerableExtensions; public sealed class ShuffleTest { +#if !SHUFFLE_EXTENSION [Fact] public void AShuffleIsEnumeratedEagerly() { @@ -15,13 +18,14 @@ public void AShuffleIsEnumeratedEagerly() Assert.Throws(() => doNotEnumerate.Shuffle()); } +#endif [Fact] public void AShuffleWithASpecificRandomDistributionAlwaysReturnsTheSameShuffle() { var source = Enumerable.Range(0, 16); - Assert.Equal(Sequence.Return(3, 2, 6, 15, 14, 0, 5, 8, 11, 7, 9, 12, 1, 13, 10, 4), source.Shuffle(new System.Random(1337))); + Assert.Equal(Sequence.Return(3, 2, 6, 15, 14, 0, 5, 8, 11, 7, 9, 12, 1, 13, 10, 4), source.Shuffle(new Random(1337))); } [Property] @@ -33,6 +37,6 @@ public Property AShuffleHasTheSameElementsAsTheSource(List source) [Property] public Property AShuffleHasTheSameLengthAsTheSource(List source) - => (source.Shuffle().Count == source.Count) + => (source.Shuffle().Count() == source.Count) .ToProperty(); } diff --git a/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs index 6c2532d69..51dd646ce 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/SlidingWindowTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/SplitTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/SplitTest.cs index f749da445..38377feb4 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/SplitTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/SplitTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/TakeEveryTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/TakeEveryTest.cs index 867e0ec3c..449c802fc 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/TakeEveryTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/TakeEveryTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/TransposeTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/TransposeTest.cs index 5b43a9703..66514297d 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/TransposeTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/TransposeTest.cs @@ -1,10 +1,10 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; public sealed class TransposeTest { - [Fact] + [Fact(Skip = "3*5 is always 15")] public void TransposeIsLazyElementsGetOnlyEnumeratedWhenRequested() { const int numberOfRows = 5; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WhereNotNullTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WhereNotNullTest.cs index 2feb4eb34..b32e47f88 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WhereNotNullTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WhereNotNullTest.cs @@ -1,5 +1,5 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WhereSelectTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WhereSelectTest.cs index a65f80234..4e2652815 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WhereSelectTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WhereSelectTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using static Funcky.Discard; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WithFirstTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WithFirstTest.cs index 6e5589284..9730631c7 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WithFirstTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WithFirstTest.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WithIndexTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WithIndexTest.cs index 4c7ce6756..437cc39d0 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WithIndexTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WithIndexTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WithLastTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WithLastTest.cs index a59e60b8e..d2a1dc584 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WithLastTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WithLastTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/WithPreviousTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/WithPreviousTest.cs index c1e111d46..f98171c65 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/WithPreviousTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/WithPreviousTest.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/EnumerableExtensions/ZipLongestTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/ZipLongestTest.cs index d395c4d03..29c577de8 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/ZipLongestTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/ZipLongestTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.EnumerableExtensions; diff --git a/Funcky.Test/Extensions/ImmutableListExtensions/IndexOfOrNoneTest.cs b/Funcky.Test/Extensions/ImmutableListExtensions/IndexOfOrNoneTest.cs index f9575a412..dbb81a8ec 100644 --- a/Funcky.Test/Extensions/ImmutableListExtensions/IndexOfOrNoneTest.cs +++ b/Funcky.Test/Extensions/ImmutableListExtensions/IndexOfOrNoneTest.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.ImmutableListExtensions; diff --git a/Funcky.Test/Extensions/ImmutableListExtensions/LastIndexOfOrNoneTest.cs b/Funcky.Test/Extensions/ImmutableListExtensions/LastIndexOfOrNoneTest.cs index 582f4badf..9c2b0cf5a 100644 --- a/Funcky.Test/Extensions/ImmutableListExtensions/LastIndexOfOrNoneTest.cs +++ b/Funcky.Test/Extensions/ImmutableListExtensions/LastIndexOfOrNoneTest.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.ImmutableListExtensions; diff --git a/Funcky.Test/Extensions/QueryableExtensions/ElementAtOrNoneTest.cs b/Funcky.Test/Extensions/QueryableExtensions/ElementAtOrNoneTest.cs index c955d1940..c2b863706 100644 --- a/Funcky.Test/Extensions/QueryableExtensions/ElementAtOrNoneTest.cs +++ b/Funcky.Test/Extensions/QueryableExtensions/ElementAtOrNoneTest.cs @@ -3,7 +3,7 @@ #if ELEMENT_AT_INDEX using Funcky.FsCheck; #endif -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.QueryableExtensions; diff --git a/Funcky.Test/Extensions/QueryableExtensions/FirstOrNoneTest.cs b/Funcky.Test/Extensions/QueryableExtensions/FirstOrNoneTest.cs index b0012e8b9..b757ac253 100644 --- a/Funcky.Test/Extensions/QueryableExtensions/FirstOrNoneTest.cs +++ b/Funcky.Test/Extensions/QueryableExtensions/FirstOrNoneTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.QueryableExtensions; diff --git a/Funcky.Test/Extensions/QueryableExtensions/LastOrNoneTest.cs b/Funcky.Test/Extensions/QueryableExtensions/LastOrNoneTest.cs index 10f2c5be6..4e4b329ba 100644 --- a/Funcky.Test/Extensions/QueryableExtensions/LastOrNoneTest.cs +++ b/Funcky.Test/Extensions/QueryableExtensions/LastOrNoneTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.QueryableExtensions; diff --git a/Funcky.Test/Extensions/QueryableExtensions/PreventAccidentalUseAsEnumerableTest.cs b/Funcky.Test/Extensions/QueryableExtensions/PreventAccidentalUseAsEnumerableTest.cs index d67cae25c..7202cf637 100644 --- a/Funcky.Test/Extensions/QueryableExtensions/PreventAccidentalUseAsEnumerableTest.cs +++ b/Funcky.Test/Extensions/QueryableExtensions/PreventAccidentalUseAsEnumerableTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.QueryableExtensions; diff --git a/Funcky.Test/Extensions/QueryableExtensions/SingleOrNoneTest.cs b/Funcky.Test/Extensions/QueryableExtensions/SingleOrNoneTest.cs index d81518a25..448fc60aa 100644 --- a/Funcky.Test/Extensions/QueryableExtensions/SingleOrNoneTest.cs +++ b/Funcky.Test/Extensions/QueryableExtensions/SingleOrNoneTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Extensions.QueryableExtensions; diff --git a/Funcky.Test/Funcky.Test.csproj b/Funcky.Test/Funcky.Test.csproj index 4a49af351..7759a2530 100644 --- a/Funcky.Test/Funcky.Test.csproj +++ b/Funcky.Test/Funcky.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0;net7.0;net6.0 + net10.0;net9.0;net8.0;net7.0;net6.0 $(TargetFrameworks);net4.8 preview enable diff --git a/Funcky.Test/FunckyAsyncPropertyAttribute.cs b/Funcky.Test/FunckyAsyncPropertyAttribute.cs new file mode 100644 index 000000000..f2132a582 --- /dev/null +++ b/Funcky.Test/FunckyAsyncPropertyAttribute.cs @@ -0,0 +1,13 @@ +#if INTEGRATED_ASYNC +using FsCheck.Xunit; + +namespace Funcky.Async.Test; + +internal sealed class FunckyAsyncPropertyAttribute : PropertyAttribute +{ + public FunckyAsyncPropertyAttribute() + { + Arbitrary = [typeof(AsyncGenerator)]; + } +} +#endif diff --git a/Funcky.Test/FunctionalClass/RetryAsyncTest.cs b/Funcky.Test/FunctionalClass/RetryAsyncTest.cs new file mode 100644 index 000000000..0c7ab785c --- /dev/null +++ b/Funcky.Test/FunctionalClass/RetryAsyncTest.cs @@ -0,0 +1,95 @@ +#if INTEGRATED_ASYNC +using Funcky.RetryPolicies; +using Funcky.Test.TestUtilities; +using static Funcky.AsyncFunctional; + +namespace Funcky.Async.Test.FunctionalClass; + +public sealed class RetryAsyncTest +{ + [Fact] + public async Task ReturnsTheValueImmediatelyIfTheProducerIsPureAndReturnsSome() + { + const int value = 10; + FunctionalAssert.Some(value, await RetryAsync(() => ValueTask.FromResult(Option.Some(value)), new DoNotRetryPolicy())); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(15)] + [InlineData(90)] + public async Task RetriesWithDoNotRetryPolicyAlwaysTriesNumberOfRetriesTimes(int numberOfRetries) + { + const string produceString = "Hello world!"; + var producer = new OptionProducer(1000, produceString); + + FunctionalAssert.None(await RetryAsync(producer.ProduceAsync, new NoDelayRetryPolicy(numberOfRetries))); + Assert.Equal(numberOfRetries + 1, producer.Called); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(15)] + [InlineData(90)] + public async Task RetriesWithDoNotRetryRetriesUntilValueProduced(int numberOfRetries) + { + const string produceString = "Hello world!"; + var producer = new OptionProducer(numberOfRetries, produceString); + + FunctionalAssert.Some(produceString, await RetryAsync(producer.ProduceAsync, new NoDelayRetryPolicy(1000))); + Assert.Equal(numberOfRetries + 1, producer.Called); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(15)] + [InlineData(90)] + public async Task RetryAsyncWithoutAnArgumentReturnsAlwaysAValueOrDoesNotReturn(int numberOfRetries) + { + const string produceString = "Hello world!"; + var producer = new OptionProducer(numberOfRetries, produceString); + + Assert.Equal(produceString, await RetryAsync(producer.ProduceAsync)); + } + + [Fact] + public async Task RetryThrowsImmediatelyWhenAlreadyCanceled() + { + using var source = new CancellationTokenSource(); + source.Cancel(); + await Assert.ThrowsAsync(async () => await RetryAsync(() => ValueTask.FromResult(Option.None), source.Token)); + } + + [Fact] + public async Task RetryThrowsWhenCanceledAfterAnArbitraryNumberOfRetries() + { + var delay = TimeSpan.FromMilliseconds(10); + + using var source = new CancellationTokenSource(); + source.CancelAfter(delay * 4); + await Assert.ThrowsAsync(async () => await RetryAsync(ProducerWithDelay(delay), source.Token)); + } + + [Fact] + public async Task RetryThrowsWhenCanceledDuringDelay() + { + var delay = TimeSpan.FromMilliseconds(10); + using var source = new CancellationTokenSource(); + source.CancelAfter(delay); + await Assert.ThrowsAnyAsync(async () => await RetryAsync(() => ValueTask.FromResult(Option.None), new ConstantDelayPolicy(10, delay), source.Token)); + } + + private static Func>> ProducerWithDelay(TimeSpan delay) + => async () => + { + await Task.Delay(delay); + return Option.None; + }; +} +#endif diff --git a/Funcky.Test/FunctionalClass/RetryTest.cs b/Funcky.Test/FunctionalClass/RetryTest.cs index 8b86ad8ce..08523688e 100644 --- a/Funcky.Test/FunctionalClass/RetryTest.cs +++ b/Funcky.Test/FunctionalClass/RetryTest.cs @@ -1,5 +1,5 @@ using Funcky.RetryPolicies; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.FunctionalClass; diff --git a/Funcky.Test/FunctionalClass/RetryWithExceptionAsyncTest.cs b/Funcky.Test/FunctionalClass/RetryWithExceptionAsyncTest.cs new file mode 100644 index 000000000..391210b78 --- /dev/null +++ b/Funcky.Test/FunctionalClass/RetryWithExceptionAsyncTest.cs @@ -0,0 +1,66 @@ +#if INTEGRATED_ASYNC +using FsCheck; +using FsCheck.Fluent; +using FsCheck.Xunit; +using Funcky.RetryPolicies; +using static Funcky.AsyncFunctional; + +namespace Funcky.Async.Test.FunctionalClass; + +public sealed class RetryWithExceptionAsyncTest +{ + [Fact] + public async Task ReturnsValueImmediatelyIfProducerDoesNotThrow() + { + const int value = 10; + Assert.Equal(value, await RetryAsync(() => value, True, new ThrowOnRetryPolicy())); + } + + [Fact] + public async Task DoesNotRetryIfPredicateReturnsFalse() + { + await Assert.ThrowsAsync(async () => await RetryAsync(Throw, False, new ThrowOnRetryPolicy())); + } + + [Property] + public Property RetriesProducerUntilItNoLongerThrows(NonNegativeInt callsUntilValueIsReturned) + { + const int value = 42; + + var called = 0; + int Producer() + => called++ == callsUntilValueIsReturned.Get + ? value + : throw new ExceptionStub(); + + return (RetryAsync(Producer, True, new NoDelayRetryPolicy(int.MaxValue)).Result == value).ToProperty(); + } + + [Property] + public Property RetriesProducerUntilRetriesAreExhausted(NonNegativeInt retries) + { + var called = 0; + Unit Producer() + { + called++; + throw new ExceptionStub(); + } + + Assert.Throws(() => RetryAsync(Producer, True, new NoDelayRetryPolicy(retries.Get)).Result); + + const int firstCall = 1; + return (called == firstCall + retries.Get).ToProperty(); + } + + private static TResult Throw() => throw new ExceptionStub(); + + private sealed class ExceptionStub : Exception; + + private sealed class ThrowOnRetryPolicy : IRetryPolicy + { + public int MaxRetries => 0; + + public TimeSpan Delay(int retryCount) => throw new InvalidOperationException("Retry is disallowed"); + } +} +#endif diff --git a/Funcky.Test/Monads/EitherTest.Monad.cs b/Funcky.Test/Monads/EitherTest.Monad.cs index b4f772b24..192d4159d 100644 --- a/Funcky.Test/Monads/EitherTest.Monad.cs +++ b/Funcky.Test/Monads/EitherTest.Monad.cs @@ -1,6 +1,6 @@ using FsCheck; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/EitherTest.Sequence.cs b/Funcky.Test/Monads/EitherTest.Sequence.cs index 87544f156..810ebcb8a 100644 --- a/Funcky.Test/Monads/EitherTest.Sequence.cs +++ b/Funcky.Test/Monads/EitherTest.Sequence.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/LazyTest.Flatten.cs b/Funcky.Test/Monads/LazyTest.Flatten.cs index 4842c099b..3f6dfdfc6 100644 --- a/Funcky.Test/Monads/LazyTest.Flatten.cs +++ b/Funcky.Test/Monads/LazyTest.Flatten.cs @@ -1,6 +1,6 @@ using FsCheck; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/LazyTest.Monad.cs b/Funcky.Test/Monads/LazyTest.Monad.cs index 462e747d3..1324a0248 100644 --- a/Funcky.Test/Monads/LazyTest.Monad.cs +++ b/Funcky.Test/Monads/LazyTest.Monad.cs @@ -1,6 +1,6 @@ using FsCheck; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/Option.FromBoolean.cs b/Funcky.Test/Monads/Option.FromBoolean.cs index d1aec72ba..9e1d58738 100644 --- a/Funcky.Test/Monads/Option.FromBoolean.cs +++ b/Funcky.Test/Monads/Option.FromBoolean.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/OptionAsyncExtensions/ToAsyncEnumerableTest.cs b/Funcky.Test/Monads/OptionAsyncExtensions/ToAsyncEnumerableTest.cs new file mode 100644 index 000000000..0b5f42bc6 --- /dev/null +++ b/Funcky.Test/Monads/OptionAsyncExtensions/ToAsyncEnumerableTest.cs @@ -0,0 +1,21 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Monads.OptionAsyncExtensions; + +public sealed class ToAsyncEnumerableTest +{ + [Fact] + public async Task ReturnsEmptyEnumerableWhenOptionIsEmpty() + { + var option = Option.None; + Assert.True(await option.ToAsyncEnumerable().NoneAsync()); + } + + [Fact] + public async Task ReturnsEnumerableWithOneElementWhenOptionHasValue() + { + const int value = 42; + var option = Option.Some(value); + Assert.Equal(value, await option.ToAsyncEnumerable().SingleAsync()); + } +} +#endif diff --git a/Funcky.Test/Monads/OptionAwaiterTest.cs b/Funcky.Test/Monads/OptionAwaiterTest.cs new file mode 100644 index 000000000..9e9563306 --- /dev/null +++ b/Funcky.Test/Monads/OptionAwaiterTest.cs @@ -0,0 +1,41 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Async.Test.Monads; + +public sealed class OptionAwaiterTest +{ + [Fact] + public async Task OptionOfVoidTaskIsAwaitable() + { + await Option.Some(Task.Delay(10)); + await Option.Some(Task.Delay(10)).ConfigureAwait(false); + await Option.None; + await Option.None.ConfigureAwait(false); + } + + [Fact] + public async Task OptionOfTaskIsAwaitable() + { + FunctionalAssert.Some(10, await Option.Some(Task.FromResult(10))); + FunctionalAssert.Some(10, await Option.Some(Task.FromResult(10)).ConfigureAwait(false)); + FunctionalAssert.None(await Option>.None.ConfigureAwait(false)); + } + + [Fact] + public async Task OptionOfVoidValueTaskIsAwaitable() + { + await Option.Some(new ValueTask(Task.Delay(10))); + await Option.Some(new ValueTask(Task.Delay(10))).ConfigureAwait(false); + await Option.None; + await Option.None.ConfigureAwait(false); + } + + [Fact] + public async Task OptionOfValueTaskIsAwaitable() + { + FunctionalAssert.Some(10, await Option.Some(ValueTask.FromResult(10))); + FunctionalAssert.Some(10, await Option.Some(ValueTask.FromResult(10)).ConfigureAwait(false)); + FunctionalAssert.None(await Option>.None); + FunctionalAssert.None(await Option>.None.ConfigureAwait(false)); + } +} +#endif diff --git a/Funcky.Test/Monads/OptionImplicitConversionTest.cs b/Funcky.Test/Monads/OptionImplicitConversionTest.cs index 597c97ef7..26fd0b47f 100644 --- a/Funcky.Test/Monads/OptionImplicitConversionTest.cs +++ b/Funcky.Test/Monads/OptionImplicitConversionTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/OptionTest.Monad.cs b/Funcky.Test/Monads/OptionTest.Monad.cs index b24a6179c..ff7725f12 100644 --- a/Funcky.Test/Monads/OptionTest.Monad.cs +++ b/Funcky.Test/Monads/OptionTest.Monad.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/OptionTest.Sequence.cs b/Funcky.Test/Monads/OptionTest.Sequence.cs index f66c6fe76..643390531 100644 --- a/Funcky.Test/Monads/OptionTest.Sequence.cs +++ b/Funcky.Test/Monads/OptionTest.Sequence.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/ReaderTest.Flatten.cs b/Funcky.Test/Monads/ReaderTest.Flatten.cs index 2f8f7fa1f..b94c577ae 100644 --- a/Funcky.Test/Monads/ReaderTest.Flatten.cs +++ b/Funcky.Test/Monads/ReaderTest.Flatten.cs @@ -1,6 +1,6 @@ using FsCheck; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/ReaderTest.Monad.cs b/Funcky.Test/Monads/ReaderTest.Monad.cs index c4aaa0431..315c0e850 100644 --- a/Funcky.Test/Monads/ReaderTest.Monad.cs +++ b/Funcky.Test/Monads/ReaderTest.Monad.cs @@ -1,6 +1,6 @@ using FsCheck; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/ResultTest.Monad.cs b/Funcky.Test/Monads/ResultTest.Monad.cs index ecfb00b89..382d355fc 100644 --- a/Funcky.Test/Monads/ResultTest.Monad.cs +++ b/Funcky.Test/Monads/ResultTest.Monad.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using Result = Funcky.Monads.Result; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Monads/ResultTest.Sequence.cs b/Funcky.Test/Monads/ResultTest.Sequence.cs index 93e38bd59..2a03384ec 100644 --- a/Funcky.Test/Monads/ResultTest.Sequence.cs +++ b/Funcky.Test/Monads/ResultTest.Sequence.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using Funcky.FsCheck; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; using Result = Funcky.Monads.Result; namespace Funcky.Test.Monads; diff --git a/Funcky.Test/Sequence/CycleMaterializedTest.cs b/Funcky.Test/Sequence/CycleMaterializedTest.cs index f9f2744e9..1c4070e8f 100644 --- a/Funcky.Test/Sequence/CycleMaterializedTest.cs +++ b/Funcky.Test/Sequence/CycleMaterializedTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/Sequence/CycleRangeTest.cs b/Funcky.Test/Sequence/CycleRangeTest.cs index 58d03c953..07bffca76 100644 --- a/Funcky.Test/Sequence/CycleRangeTest.cs +++ b/Funcky.Test/Sequence/CycleRangeTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/Sequence/CycleTest.cs b/Funcky.Test/Sequence/CycleTest.cs index 0135ca8b7..ff50fa261 100644 --- a/Funcky.Test/Sequence/CycleTest.cs +++ b/Funcky.Test/Sequence/CycleTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/Sequence/RepeatMaterializedTest.cs b/Funcky.Test/Sequence/RepeatMaterializedTest.cs index 67d849fce..b90f06cbd 100644 --- a/Funcky.Test/Sequence/RepeatMaterializedTest.cs +++ b/Funcky.Test/Sequence/RepeatMaterializedTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/Sequence/RepeatRangeTest.cs b/Funcky.Test/Sequence/RepeatRangeTest.cs index 01a712fe3..e4b08ec35 100644 --- a/Funcky.Test/Sequence/RepeatRangeTest.cs +++ b/Funcky.Test/Sequence/RepeatRangeTest.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Test/TestUtilities/AssertIsCancellationRequestedAsyncSequence.cs b/Funcky.Test/TestUtilities/AssertIsCancellationRequestedAsyncSequence.cs new file mode 100644 index 000000000..1b6dcdb59 --- /dev/null +++ b/Funcky.Test/TestUtilities/AssertIsCancellationRequestedAsyncSequence.cs @@ -0,0 +1,14 @@ +#if INTEGRATED_ASYNC +#pragma warning disable CS1998 + +namespace Funcky.Async.Test.TestUtilities; + +internal sealed class AssertIsCancellationRequestedAsyncSequence : IAsyncEnumerable +{ + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + Assert.True(cancellationToken.IsCancellationRequested, "cancellationToken.IsCancellationRequested"); + yield break; + } +} +#endif diff --git a/Funcky.Test/TestUtilities/AsyncAssert.cs b/Funcky.Test/TestUtilities/AsyncAssert.cs new file mode 100644 index 000000000..1eea1eea2 --- /dev/null +++ b/Funcky.Test/TestUtilities/AsyncAssert.cs @@ -0,0 +1,85 @@ +#if INTEGRATED_ASYNC +using Xunit.Sdk; + +namespace Funcky.Async.Test.TestUtilities; + +internal static class AsyncAssert +{ + public static async Task Empty(IAsyncEnumerable asyncSequence) + { + var asyncEnumerator = asyncSequence.GetAsyncEnumerator(); + try + { + if (await asyncEnumerator.MoveNextAsync()) + { + throw EmptyException.ForNonEmptyCollection(collection: await FormatCollectionStart(asyncSequence)); + } + } + finally + { + await asyncEnumerator.DisposeAsync(); + } + } + + public static async Task NotEmpty(IAsyncEnumerable asyncSequence) + { + var asyncEnumerator = asyncSequence.GetAsyncEnumerator(); + try + { + if (!await asyncEnumerator.MoveNextAsync()) + { + throw NotEmptyException.ForNonEmptyCollection(); + } + } + finally + { + await asyncEnumerator.DisposeAsync(); + } + } + + public static async Task Collection(IAsyncEnumerable asyncSequence, params Action[] elementInspectors) + { + var elements = await asyncSequence.ToListAsync(); + Assert.Collection(elements, elementInspectors); + } + + public static async Task Single(IAsyncEnumerable asyncSequence) + { + await using var asyncEnumerator = asyncSequence.GetAsyncEnumerator(); + + if (await asyncEnumerator.MoveNextAsync() is false) + { + throw SingleException.Empty(expected: null, collection: string.Empty); + } + + var result = asyncEnumerator.Current; + + if (await asyncEnumerator.MoveNextAsync()) + { + var actual = await MaterializeCollectionStart(asyncSequence); + throw SingleException.MoreThanOne(expected: null, collection: FormatCollectionStart(actual), count: actual.Count, matchIndices: Array.Empty()); + } + + return result; + } + + public static async Task Equal(IAsyncEnumerable expectedResult, IAsyncEnumerable actual) + => Assert.Equal(await expectedResult.ToListAsync(), await actual.ToListAsync()); + + private static async Task> MaterializeCollectionStart(IAsyncEnumerable asyncSequence) + { + // This should *ideally* be kept in sync with XUnit's `ArgumentFormatter.MAX_ENUMERABLE_LENGTH + 1` (which is private). + const int maxEnumerableLength = 6; + return await asyncSequence.Take(maxEnumerableLength).ToListAsync(); + } + + private static async Task FormatCollectionStart(IAsyncEnumerable asyncSequence) + => FormatCollectionStart(await MaterializeCollectionStart(asyncSequence)); + + private static string FormatCollectionStart(IEnumerable sequence) + { + using var tracker = sequence.AsTracker(); + return tracker.FormatStart(); + } +} +#endif diff --git a/Funcky.Test/TestUtilities/AsyncEnumerateOnce.cs b/Funcky.Test/TestUtilities/AsyncEnumerateOnce.cs new file mode 100644 index 000000000..ceb703d8a --- /dev/null +++ b/Funcky.Test/TestUtilities/AsyncEnumerateOnce.cs @@ -0,0 +1,53 @@ +#if INTEGRATED_ASYNC +using Xunit.Sdk; + +namespace Funcky.Async.Test.TestUtilities; + +public class AsyncEnumerateOnce +{ + public static AsyncEnumerateOnce Create(IEnumerable sequence) + where T : notnull + => new(new Queue(sequence)); +} + +public class AsyncEnumerateOnce : IAsyncEnumerable + where T : notnull +{ + private readonly Queue _once; + private bool _first = true; + + internal AsyncEnumerateOnce(Queue queue) + => _once = queue; + +#pragma warning disable CS1998 + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) +#pragma warning restore CS1998 + { + ValidateFirst(); + + while (true) + { + if (_once.TryDequeue(out var value)) + { + yield return value; + } + else + { + break; + } + } + } + + private void ValidateFirst() + { + if (_first) + { + _first = false; + } + else + { + throw new XunitException("Sequence was unexpectedly enumerated a second time."); + } + } +} +#endif diff --git a/Funcky.Test/TestUtils/CheckAssert.cs b/Funcky.Test/TestUtilities/CheckAssert.cs similarity index 97% rename from Funcky.Test/TestUtils/CheckAssert.cs rename to Funcky.Test/TestUtilities/CheckAssert.cs index 813412556..d059f243c 100644 --- a/Funcky.Test/TestUtils/CheckAssert.cs +++ b/Funcky.Test/TestUtilities/CheckAssert.cs @@ -1,7 +1,7 @@ using FsCheck; using FsCheck.Fluent; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal static class CheckAssert { diff --git a/Funcky.Test/TestUtils/CountCreation.cs b/Funcky.Test/TestUtilities/CountCreation.cs similarity index 80% rename from Funcky.Test/TestUtils/CountCreation.cs rename to Funcky.Test/TestUtilities/CountCreation.cs index 44f805459..d5f46f17e 100644 --- a/Funcky.Test/TestUtils/CountCreation.cs +++ b/Funcky.Test/TestUtilities/CountCreation.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class CountCreation { diff --git a/Funcky.Test/TestUtils/EnumerableExtensions.cs b/Funcky.Test/TestUtilities/EnumerableExtensions.cs similarity index 90% rename from Funcky.Test/TestUtils/EnumerableExtensions.cs rename to Funcky.Test/TestUtilities/EnumerableExtensions.cs index 33b782952..f17b06131 100644 --- a/Funcky.Test/TestUtils/EnumerableExtensions.cs +++ b/Funcky.Test/TestUtilities/EnumerableExtensions.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal static class EnumerableExtensions { diff --git a/Funcky.Test/TestUtils/EnumerateOnce.cs b/Funcky.Test/TestUtilities/EnumerateOnce.cs similarity index 96% rename from Funcky.Test/TestUtils/EnumerateOnce.cs rename to Funcky.Test/TestUtilities/EnumerateOnce.cs index e3391034a..cc9fc7c15 100644 --- a/Funcky.Test/TestUtils/EnumerateOnce.cs +++ b/Funcky.Test/TestUtilities/EnumerateOnce.cs @@ -1,7 +1,7 @@ using System.Collections; using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; public class EnumerateOnce { diff --git a/Funcky.Test/TestUtils/EverythingIsEqual.cs b/Funcky.Test/TestUtilities/EverythingIsEqual.cs similarity index 80% rename from Funcky.Test/TestUtils/EverythingIsEqual.cs rename to Funcky.Test/TestUtilities/EverythingIsEqual.cs index 2ac803f31..b2a356489 100644 --- a/Funcky.Test/TestUtils/EverythingIsEqual.cs +++ b/Funcky.Test/TestUtilities/EverythingIsEqual.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal class EverythingIsEqual : IEqualityComparer { diff --git a/Funcky.Test/TestUtils/FailOnCall.cs b/Funcky.Test/TestUtilities/FailOnCall.cs similarity index 94% rename from Funcky.Test/TestUtils/FailOnCall.cs rename to Funcky.Test/TestUtilities/FailOnCall.cs index fe540ee1c..a3e8d7aa3 100644 --- a/Funcky.Test/TestUtils/FailOnCall.cs +++ b/Funcky.Test/TestUtilities/FailOnCall.cs @@ -1,6 +1,6 @@ using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal static class FailOnCall { diff --git a/Funcky.Test/TestUtilities/FailOnEnumerateAsyncSequence.cs b/Funcky.Test/TestUtilities/FailOnEnumerateAsyncSequence.cs new file mode 100644 index 000000000..88c7ac4b8 --- /dev/null +++ b/Funcky.Test/TestUtilities/FailOnEnumerateAsyncSequence.cs @@ -0,0 +1,16 @@ +#if INTEGRATED_ASYNC +using Xunit.Sdk; + +namespace Funcky.Async.Test.TestUtilities; + +internal sealed class FailOnEnumerateAsyncSequence : IAsyncEnumerable +{ +#pragma warning disable 1998, 162 + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + throw new XunitException("Sequence was unexpectedly enumerated."); + yield break; + } +#pragma warning restore 1998, 162 +} +#endif diff --git a/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs b/Funcky.Test/TestUtilities/FailOnEnumerateCollection.cs similarity index 94% rename from Funcky.Test/TestUtils/FailOnEnumerateCollection.cs rename to Funcky.Test/TestUtilities/FailOnEnumerateCollection.cs index 84c1eba0f..431eb0efe 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateCollection.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerateCollection.cs @@ -1,7 +1,7 @@ using System.Collections; using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal record FailOnEnumerateCollection(int Count) : ICollection { diff --git a/Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs b/Funcky.Test/TestUtilities/FailOnEnumerateCollectionWrapper.cs similarity index 95% rename from Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs rename to Funcky.Test/TestUtilities/FailOnEnumerateCollectionWrapper.cs index d829d0e27..c530404c3 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerateCollectionWrapper.cs @@ -1,7 +1,7 @@ using System.Collections; using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal class FailOnEnumerateCollectionWrapper(ICollection collection) : ICollection { diff --git a/Funcky.Test/TestUtils/FailOnEnumerateList.cs b/Funcky.Test/TestUtilities/FailOnEnumerateList.cs similarity index 92% rename from Funcky.Test/TestUtils/FailOnEnumerateList.cs rename to Funcky.Test/TestUtilities/FailOnEnumerateList.cs index ef1e8bab9..7065e29d9 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateList.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerateList.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed record FailOnEnumerateList(int Count) : FailOnEnumerateCollection(Count), IList { diff --git a/Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs b/Funcky.Test/TestUtilities/FailOnEnumerateListWrapper.cs similarity index 92% rename from Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs rename to Funcky.Test/TestUtilities/FailOnEnumerateListWrapper.cs index af6edefd8..8b5a45fb4 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerateListWrapper.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class FailOnEnumerateListWrapper(IList list) : FailOnEnumerateCollectionWrapper(list), IList { diff --git a/Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs b/Funcky.Test/TestUtilities/FailOnEnumerateReadOnlyCollection.cs similarity index 89% rename from Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs rename to Funcky.Test/TestUtilities/FailOnEnumerateReadOnlyCollection.cs index a21fed579..de70a08b8 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerateReadOnlyCollection.cs @@ -1,7 +1,7 @@ using System.Collections; using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal record FailOnEnumerateReadOnlyCollection(int Count) : IReadOnlyCollection { diff --git a/Funcky.Test/TestUtils/FailOnEnumerationSequence.cs b/Funcky.Test/TestUtilities/FailOnEnumerationSequence.cs similarity index 93% rename from Funcky.Test/TestUtils/FailOnEnumerationSequence.cs rename to Funcky.Test/TestUtilities/FailOnEnumerationSequence.cs index b409ae13f..c215238e5 100644 --- a/Funcky.Test/TestUtils/FailOnEnumerationSequence.cs +++ b/Funcky.Test/TestUtilities/FailOnEnumerationSequence.cs @@ -1,7 +1,7 @@ using System.Collections; using Xunit.Sdk; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class FailOnEnumerationSequence : IEnumerable { diff --git a/Funcky.Test/TestUtilities/IMyInterface.cs b/Funcky.Test/TestUtilities/IMyInterface.cs new file mode 100644 index 000000000..79a9456c3 --- /dev/null +++ b/Funcky.Test/TestUtilities/IMyInterface.cs @@ -0,0 +1,3 @@ +namespace Funcky.Test.TestUtilities; + +internal interface IMyInterface; diff --git a/Funcky.Test/TestUtils/MyClass.cs b/Funcky.Test/TestUtilities/MyClass.cs similarity index 55% rename from Funcky.Test/TestUtils/MyClass.cs rename to Funcky.Test/TestUtilities/MyClass.cs index 8558fcd8c..1dc334155 100644 --- a/Funcky.Test/TestUtils/MyClass.cs +++ b/Funcky.Test/TestUtilities/MyClass.cs @@ -1,3 +1,3 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class MyClass : IMyInterface; diff --git a/Funcky.Test/TestUtils/OptionProducer.cs b/Funcky.Test/TestUtilities/OptionProducer.cs similarity index 53% rename from Funcky.Test/TestUtils/OptionProducer.cs rename to Funcky.Test/TestUtilities/OptionProducer.cs index 501f0bad0..ba8129e56 100644 --- a/Funcky.Test/TestUtils/OptionProducer.cs +++ b/Funcky.Test/TestUtilities/OptionProducer.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class OptionProducer(int retriesNeeded, T result) where T : notnull @@ -11,4 +11,13 @@ public Option Produce() return Option.FromBoolean(retriesNeeded == (Called - 1), result); } + +#if INTEGRATED_ASYNC + public ValueTask> ProduceAsync() + { + Called += 1; + + return ValueTask.FromResult(Option.FromBoolean(retriesNeeded == (Called - 1), result)); + } +#endif } diff --git a/Funcky.Test/TestUtils/QueryableExtensions.cs b/Funcky.Test/TestUtilities/QueryableExtensions.cs similarity index 97% rename from Funcky.Test/TestUtils/QueryableExtensions.cs rename to Funcky.Test/TestUtilities/QueryableExtensions.cs index 45e676335..fdad01e19 100644 --- a/Funcky.Test/TestUtils/QueryableExtensions.cs +++ b/Funcky.Test/TestUtilities/QueryableExtensions.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Linq.Expressions; -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal static class QueryableExtensions { diff --git a/Funcky.Test/TestUtilities/RepeatingSequence.cs b/Funcky.Test/TestUtilities/RepeatingSequence.cs new file mode 100644 index 000000000..63226c601 --- /dev/null +++ b/Funcky.Test/TestUtilities/RepeatingSequence.cs @@ -0,0 +1,48 @@ +namespace Funcky.Test.TestUtilities; + +public static class RepeatingSequence +{ + public static RepeatingSequenceHelper IsSequenceRepeating(this IEnumerable sequence, IEnumerable pattern) + => new(sequence, pattern); + +#if INTEGRATED_ASYNC + public static RepeatingAsyncSequenceHelper IsSequenceRepeating(this IAsyncEnumerable sequence, IAsyncEnumerable pattern) + => new(sequence, pattern); +#endif + + public class RepeatingSequenceHelper(IEnumerable sequence, IEnumerable pattern) + { + public bool NTimes(int count) + => Enumerable + .Range(0, count) + .Aggregate(true, AggregateEquality); + + public bool AggregateEquality(bool b, int i) + => b && sequence + .Skip(i * pattern.Count()) + .Zip(pattern, (l, r) => l == r) + .All(Identity); + } + +#if INTEGRATED_ASYNC + public class RepeatingAsyncSequenceHelper + { + private readonly IAsyncEnumerable _sequence; + private readonly IAsyncEnumerable _pattern; + + internal RepeatingAsyncSequenceHelper(IAsyncEnumerable sequence, IAsyncEnumerable pattern) + => (_sequence, _pattern) = (sequence, pattern); + + public async Task NTimes(int count) + => await AsyncEnumerable + .Range(0, count) + .AggregateAsync(true, AggregateEquality); + + public async ValueTask AggregateEquality(bool b, int i, CancellationToken cancellationToken) + => b && await _sequence + .Skip(i * await _pattern.CountAsync(cancellationToken)) + .Zip(_pattern, (l, r) => l == r) + .AllAsync(Identity, cancellationToken); + } +#endif +} diff --git a/Funcky.Test/TestUtils/SideEffect.cs b/Funcky.Test/TestUtilities/SideEffect.cs similarity index 76% rename from Funcky.Test/TestUtils/SideEffect.cs rename to Funcky.Test/TestUtilities/SideEffect.cs index e0d953e1d..ef6d91501 100644 --- a/Funcky.Test/TestUtils/SideEffect.cs +++ b/Funcky.Test/TestUtilities/SideEffect.cs @@ -1,4 +1,4 @@ -namespace Funcky.Test.TestUtils; +namespace Funcky.Test.TestUtilities; internal sealed class SideEffect { diff --git a/Funcky.Test/TestUtils/IMyInterface.cs b/Funcky.Test/TestUtils/IMyInterface.cs deleted file mode 100644 index 1a748f0f9..000000000 --- a/Funcky.Test/TestUtils/IMyInterface.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Funcky.Test.TestUtils; - -internal interface IMyInterface; diff --git a/Funcky.Test/TestUtils/RepeatingSequence.cs b/Funcky.Test/TestUtils/RepeatingSequence.cs deleted file mode 100644 index b49702a8a..000000000 --- a/Funcky.Test/TestUtils/RepeatingSequence.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Funcky.Test.TestUtils; - -public static class RepeatingSequence -{ - public static RepeatingSequenceHelper IsSequenceRepeating(this IEnumerable sequence, IEnumerable pattern) - => new(sequence, pattern); - - public class RepeatingSequenceHelper(IEnumerable sequence, IEnumerable pattern) - { - public bool NTimes(int count) - => Enumerable - .Range(0, count) - .Aggregate(true, AggregateEquality); - - public bool AggregateEquality(bool b, int i) - => b && sequence - .Skip(i * pattern.Count()) - .Zip(pattern, (l, r) => l == r) - .All(Identity); - } -} diff --git a/Funcky.Test/UnitTest.cs b/Funcky.Test/UnitTest.cs index c574756d3..eadb62ac3 100644 --- a/Funcky.Test/UnitTest.cs +++ b/Funcky.Test/UnitTest.cs @@ -1,4 +1,4 @@ -using Funcky.Test.TestUtils; +using Funcky.Test.TestUtilities; namespace Funcky.Test; diff --git a/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj b/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj index e23fb008a..e6a9f8c64 100644 --- a/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj +++ b/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 preview enable false diff --git a/Funcky.Xunit.v3.Test/Funcky.Xunit.v3.Test.csproj b/Funcky.Xunit.v3.Test/Funcky.Xunit.v3.Test.csproj index d2e42bb3f..040468773 100644 --- a/Funcky.Xunit.v3.Test/Funcky.Xunit.v3.Test.csproj +++ b/Funcky.Xunit.v3.Test/Funcky.Xunit.v3.Test.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 preview enable false diff --git a/Funcky/AsyncSequence/AsyncSequence.Concat.cs b/Funcky/AsyncSequence/AsyncSequence.Concat.cs new file mode 100644 index 000000000..1fa0c689c --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.Concat.cs @@ -0,0 +1,37 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +public static partial class AsyncSequence +{ + /// + /// Concatenates multiple sequences together. + /// + [Pure] + public static IAsyncEnumerable Concat(params IAsyncEnumerable[] sources) + => Concat(sources.AsEnumerable()); + + /// + /// Concatenates multiple sequences together. + /// + [Pure] + public static IAsyncEnumerable Concat(IAsyncEnumerable> sources) + => sources + .SelectMany(Identity); + + /// + /// Concatenates multiple sequences together. + /// + [Pure] + public static IAsyncEnumerable Concat(IEnumerable> sources) + => sources.ToAsyncEnumerable() + .SelectMany(Identity); + + /// + /// Concatenates multiple sequences together. + /// + [Pure] + public static IAsyncEnumerable Concat(IAsyncEnumerable> sources) + => sources + .SelectMany(AsyncEnumerable.ToAsyncEnumerable); +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.Cycle.cs b/Funcky/AsyncSequence/AsyncSequence.Cycle.cs new file mode 100644 index 000000000..fc20dd3b9 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.Cycle.cs @@ -0,0 +1,18 @@ +#if INTEGRATED_ASYNC +using static Funcky.ValueTaskFactory; + +namespace Funcky; + +public static partial class AsyncSequence +{ + /// + /// Cycles the same element over and over again as an endless generator. + /// + /// Type of the element to be cycled. + /// The element to be cycled. + /// Returns an infinite IEnumerable cycling through the same elements. + [Pure] + public static IAsyncEnumerable Cycle(TResult element) + => Successors(element, ValueTaskFromResult); +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.CycleRange.cs b/Funcky/AsyncSequence/AsyncSequence.CycleRange.cs new file mode 100644 index 000000000..1b85eb332 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.CycleRange.cs @@ -0,0 +1,116 @@ +#if INTEGRATED_ASYNC +#pragma warning disable SA1010 // StyleCop support for collection expressions is missing +namespace Funcky; + +public static partial class AsyncSequence +{ + /// + /// Generates a sequence that contains the same sequence of elements over and over again as an endless generator. + /// + /// Type of the elements to be cycled. + /// The sequence of elements which are cycled. Throws an exception if the sequence is empty. + /// Returns an infinite IEnumerable repeating the same sequence of elements. + [Pure] + public static IAsyncBuffer CycleRange(IAsyncEnumerable sequence) + => CycleBuffer.Create(sequence); + + private static class CycleBuffer + { + public static AsyncCycleBuffer Create(IAsyncEnumerable source, Option maxCycles = default) + => new(source, maxCycles); + } + + private sealed class AsyncCycleBuffer(IAsyncEnumerable source, Option maxCycles = default) : IAsyncBuffer + { + private readonly List _buffer = []; + private readonly IAsyncEnumerator _source = source.GetAsyncEnumerator(); + + private bool _disposed; + + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await _source.DisposeAsync().ConfigureAwait(false); + _buffer.Clear(); + _disposed = true; + } + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + + return GetEnumeratorInternal(); + } + + private async IAsyncEnumerator GetEnumeratorInternal() + { + if (HasNoCycles()) + { + yield break; + } + + for (var index = 0; true; ++index) + { + ThrowIfDisposed(); + + if (index == _buffer.Count) + { + if (await _source.MoveNextAsync().ConfigureAwait(false)) + { + _buffer.Add(_source.Current); + } + else + { + break; + } + } + + yield return _buffer[index]; + } + + if (_buffer.Count is 0) + { + if (maxCycles.Match(none: true, some: False)) + { + throw new InvalidOperationException("you cannot cycle an empty enumerable"); + } + else + { + yield break; + } + } + + // this can change on Dispose! + var bufferCount = _buffer.Count; + + for (int cycle = 1; IsCycling(cycle); ++cycle) + { + for (var index = 0; index < bufferCount; ++index) + { + ThrowIfDisposed(); + + yield return _buffer[index]; + } + } + } + + private bool HasNoCycles() + => maxCycles.Match(none: false, some: maxCycles => maxCycles is 0); + + private bool IsCycling(int cycle) + => maxCycles.Match( + none: true, + some: maxCycles => cycle < maxCycles); + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(CycleBuffer)); + } + } + } +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.FromNullable.cs b/Funcky/AsyncSequence/AsyncSequence.FromNullable.cs new file mode 100644 index 000000000..8ed3bc994 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.FromNullable.cs @@ -0,0 +1,22 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +public static partial class AsyncSequence +{ + /// An consisting of a single item or zero items. + [Pure] + public static IAsyncEnumerable FromNullable(TResult? element) + where TResult : class + => element is null + ? AsyncEnumerable.Empty() + : Return(element); + + /// + [Pure] + public static IAsyncEnumerable FromNullable(TResult? element) + where TResult : struct + => element.HasValue + ? Return(element.Value) + : AsyncEnumerable.Empty(); +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.RepeatRange.cs b/Funcky/AsyncSequence/AsyncSequence.RepeatRange.cs new file mode 100644 index 000000000..fbd7e4229 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.RepeatRange.cs @@ -0,0 +1,17 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +public static partial class AsyncSequence +{ + /// + /// Generates a sequence that contains the same sequence of elements the given number of times. + /// + /// Type of the elements to be repeated. + /// The sequence of elements to be repeated. + /// The number of times to repeat the value in the generated sequence. + /// Returns an infinite IEnumerable cycling through the same elements. + [Pure] + public static IAsyncBuffer RepeatRange(IAsyncEnumerable source, int count) + => CycleBuffer.Create(source, count); +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.Return.cs b/Funcky/AsyncSequence/AsyncSequence.Return.cs new file mode 100644 index 000000000..84862ff77 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.Return.cs @@ -0,0 +1,14 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +public static partial class AsyncSequence +{ + [Pure] + public static IAsyncEnumerable Return(TResult element) + => Return(elements: element); + + [Pure] + public static IAsyncEnumerable Return(params TResult[] elements) + => elements.ToAsyncEnumerable(); +} +#endif diff --git a/Funcky/AsyncSequence/AsyncSequence.Successors.cs b/Funcky/AsyncSequence/AsyncSequence.Successors.cs new file mode 100644 index 000000000..6557760e7 --- /dev/null +++ b/Funcky/AsyncSequence/AsyncSequence.Successors.cs @@ -0,0 +1,49 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +public static partial class AsyncSequence +{ + /// + /// Generates a sequence based on a function stopping at the first value. + /// This is essentially the inverse operation of an . + /// + /// The first element of the sequence. + /// Generates the next element of the sequence or based on the previous item. + /// Use on the result if you don't want the first item to be included. + [Pure] + public static async IAsyncEnumerable Successors(Option first, Func>> successor) + where TResult : notnull + { + var item = first; + while (item.TryGetValue(out var itemValue)) + { + yield return itemValue; + item = await successor(itemValue).ConfigureAwait(false); + } + } + + /// + [Pure] + public static IAsyncEnumerable Successors(TResult first, Func>> successor) + where TResult : notnull + => Successors(Option.Some(first), successor); + + /// + [Pure] + public static IAsyncEnumerable Successors(Option first, Func> successor) + where TResult : notnull + => Successors(first, async previous => Option.Some(await successor(previous).ConfigureAwait(false))); + + /// + [Pure] + public static async IAsyncEnumerable Successors(TResult first, Func> successor) + { + var item = first; + while (true) + { + yield return item; + item = await successor(item).ConfigureAwait(false); + } + } +} +#endif diff --git a/Funcky/CompatibilitySuppressions.xml b/Funcky/CompatibilitySuppressions.xml index 46401cc6c..da67a718f 100644 --- a/Funcky/CompatibilitySuppressions.xml +++ b/Funcky/CompatibilitySuppressions.xml @@ -7,6 +7,12 @@ lib/net5.0/Funcky.dll lib/net6.0/Funcky.dll + + CP0002 + M:Funcky.Extensions.EnumerableExtensions.Shuffle``1(System.Collections.Generic.IEnumerable{``0}) + lib/net9.0/Funcky.dll + lib/net10.0/Funcky.dll + CP0021 M:Funcky.Extensions.DictionaryExtensions.GetValueOrNone``2(System.Collections.Generic.IDictionary{``0,``1},``0)``0:notnull diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupBy.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupBy.cs new file mode 100644 index 000000000..e9c27d4e4 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupBy.cs @@ -0,0 +1,198 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Groups adjacent elements of a source sequence with same key specified by the key selector function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector) + => AdjacentGroupByInternal(source, keySelector, Identity, CreateGrouping, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and compares the keys by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// An to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + IEqualityComparer comparer) + => AdjacentGroupByInternal(source, keySelector, Identity, CreateGrouping, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a key selector function. The keys are compared by using a comparer and each group's elements are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func elementSelector) + => AdjacentGroupByInternal(source, keySelector, elementSelector, CreateGrouping, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and projects the elements for each group by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func elementSelector, + IEqualityComparer comparer) + => AdjacentGroupByInternal(source, keySelector, elementSelector, CreateGrouping, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the result value returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func, TResult> resultSelector) + => AdjacentGroupByInternal(source, keySelector, Identity, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the elements in each . + /// The type of the result value returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func elementSelector, + Func, TResult> resultSelector) + => AdjacentGroupByInternal(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The keys are compared by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the result value returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to create a result value from each group. + /// An to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func, TResult> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByInternal(source, keySelector, Identity, resultSelector, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. Key values are compared by using a specified comparer, and the elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by . + /// The type of the elements in each . + /// The type of the result value returned by . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// An to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupBy( + this IAsyncEnumerable source, + Func keySelector, + Func elementSelector, + Func, TResult> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByInternal(source, keySelector, elementSelector, resultSelector, comparer); + + private static async IAsyncEnumerable AdjacentGroupByInternal( + this IAsyncEnumerable source, + Func keySelector, + Func elementSelector, + Func, TResult> resultSelector, + IEqualityComparer comparer, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken); + await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false); + + if (!await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } + + var (group, key) = CreateGroupAndKey(keySelector, elementSelector, asyncEnumerator); + + while (await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + if (comparer.Equals(key, keySelector(asyncEnumerator.Current))) + { + group = group.Add(elementSelector(asyncEnumerator.Current)); + } + else + { + yield return resultSelector(key, group); + (group, key) = CreateGroupAndKey(keySelector, elementSelector, asyncEnumerator); + } + } + + yield return resultSelector(key, group); + } + + private static Grouping CreateGrouping(TKey key, IImmutableList elements) + => new(key, elements); + + private static (IImmutableList Group, TKey Key) CreateGroupAndKey(Func keySelector, Func elementSelector, IAsyncEnumerator enumerator) + { + var group = ImmutableList.Create(elementSelector(enumerator.Current)); + var key = keySelector(enumerator.Current); + + return (group, key); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwait.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwait.cs new file mode 100644 index 000000000..21e798c41 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwait.cs @@ -0,0 +1,199 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using static Funcky.ValueTaskFactory; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Groups adjacent elements of a source sequence with same key specified by the key selector function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The source sequence. + /// A function to extract the key for each element. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector) + => AdjacentGroupByAwaitInternal(source, keySelector, ValueTaskFromResult, CreateGroupingAwait, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and compares the keys by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The source sequence. + /// A function to extract the key for each element. + /// An IEqualityComparer{T} to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitInternal(source, keySelector, ValueTaskFromResult, CreateGroupingAwait, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a key selector function. The keys are compared by using a comparer and each group's elements are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector) + => AdjacentGroupByAwaitInternal(source, keySelector, elementSelector, CreateGroupingAwait, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and projects the elements for each group by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An IEqualityComparer{T} to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitInternal(source, keySelector, elementSelector, CreateGroupingAwait, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func, ValueTask> resultSelector) + => AdjacentGroupByAwaitInternal(source, keySelector, ValueTaskFromResult, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, ValueTask> resultSelector) + => AdjacentGroupByAwaitInternal(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The keys are compared by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to create a result value from each group. + /// An IEqualityComparer{T} to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func, ValueTask> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitInternal(source, keySelector, ValueTaskFromResult, resultSelector, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. Key values are compared by using a specified comparer, and the elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// An IEqualityComparer{T} to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwait( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, ValueTask> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitInternal(source, keySelector, elementSelector, resultSelector, comparer); + + private static async IAsyncEnumerable AdjacentGroupByAwaitInternal( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, ValueTask> resultSelector, + IEqualityComparer comparer, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken); + await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false); + + if (!await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } + + var (group, key) = await CreateGroupAndKeyAsync(keySelector, elementSelector, asyncEnumerator).ConfigureAwait(false); + + while (await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + if (comparer.Equals(key, await keySelector(asyncEnumerator.Current).ConfigureAwait(false))) + { + group = group.Add(await elementSelector(asyncEnumerator.Current).ConfigureAwait(false)); + } + else + { + yield return await resultSelector(key, group).ConfigureAwait(false); + (group, key) = await CreateGroupAndKeyAsync(keySelector, elementSelector, asyncEnumerator).ConfigureAwait(false); + } + } + + yield return await resultSelector(key, group).ConfigureAwait(false); + } + + private static ValueTask> CreateGroupingAwait(TKey key, IImmutableList elements) + => ValueTaskFromResult(new Grouping(key, elements)); + + private static async Task<(IImmutableList Group, TKey Key)> CreateGroupAndKeyAsync(Func> keySelector, Func> elementSelector, IAsyncEnumerator enumerator) + { + var group = ImmutableList.Create(await elementSelector(enumerator.Current).ConfigureAwait(false)); + var key = await keySelector(enumerator.Current).ConfigureAwait(false); + + return (group, key); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwaitWithCancellation.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwaitWithCancellation.cs new file mode 100644 index 000000000..2da616fee --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AdjacentGroupByAwaitWithCancellation.cs @@ -0,0 +1,206 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using static Funcky.ValueTaskFactory; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Groups adjacent elements of a source sequence with same key specified by the key selector function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The source sequence. + /// A function to extract the key for each element. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, ValueTaskFromResult, CreateGroupingAwait, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and compares the keys by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The source sequence. + /// A function to extract the key for each element. + /// An IEqualityComparer{T} to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, ValueTaskFromResult, CreateGroupingAwait, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a key selector function. The keys are compared by using a comparer and each group's elements are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, elementSelector, CreateGroupingAwait, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and projects the elements for each group by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// An IEqualityComparer{T} to compare keys. + /// An where each element is an object containing a sequence of objects and a key. + [Pure] + public static IAsyncEnumerable> AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, elementSelector, CreateGroupingAwait, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func, CancellationToken, ValueTask> resultSelector) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, ValueTaskFromResult, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, CancellationToken, ValueTask> resultSelector) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. The keys are compared by using a specified comparer. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to create a result value from each group. + /// An IEqualityComparer{T} to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func, CancellationToken, ValueTask> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, ValueTaskFromResult, resultSelector, comparer); + + /// + /// Groups adjacent elements of a source sequence according to a specified key selector function and creates a result value from each group and its key. Key values are compared by using a specified comparer, and the elements of each group are projected by using a specified function. + /// + /// Type of the elements in sequence. + /// The type of the key returned by keySelector. + /// The type of the elements in each . + /// The type of the result value returned by resultSelector. + /// The source sequence. + /// A function to extract the key for each element. + /// A function to map each source element to an element in the . + /// A function to create a result value from each group. + /// An IEqualityComparer{T} to compare keys. + /// A collection of elements of type TResult where each element represents a projection over a group and its key. + [Pure] + public static IAsyncEnumerable AdjacentGroupByAwaitWithCancellation( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, CancellationToken, ValueTask> resultSelector, + IEqualityComparer comparer) + => AdjacentGroupByAwaitWithCancellationInternal(source, keySelector, elementSelector, resultSelector, comparer); + + private static async IAsyncEnumerable AdjacentGroupByAwaitWithCancellationInternal( + this IAsyncEnumerable source, + Func> keySelector, + Func> elementSelector, + Func, CancellationToken, ValueTask> resultSelector, + IEqualityComparer comparer, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken); + await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false); + + if (!await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } + + var (group, key) = await CreateGroupAndKeyAsync(keySelector, elementSelector, asyncEnumerator, cancellationToken).ConfigureAwait(false); + + while (await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + if (comparer.Equals(key, await keySelector(asyncEnumerator.Current, cancellationToken).ConfigureAwait(false))) + { + group = group.Add(await elementSelector(asyncEnumerator.Current, cancellationToken).ConfigureAwait(false)); + } + else + { + yield return await resultSelector(key, group, cancellationToken).ConfigureAwait(false); + (group, key) = await CreateGroupAndKeyAsync(keySelector, elementSelector, asyncEnumerator, cancellationToken).ConfigureAwait(false); + } + } + + yield return await resultSelector(key, group, cancellationToken).ConfigureAwait(false); + } + + private static ValueTask> CreateGroupingAwait( + TKey key, + IImmutableList elements, + CancellationToken cancellationToken) + => ValueTaskFromResult(new Grouping(key, elements), cancellationToken); + + private static async Task<(IImmutableList Group, TKey Key)> CreateGroupAndKeyAsync( + Func> keySelector, + Func> elementSelector, + IAsyncEnumerator enumerator, + CancellationToken cancellationToken) + { + var group = ImmutableList.Create(await elementSelector(enumerator.Current, cancellationToken).ConfigureAwait(false)); + var key = await keySelector(enumerator.Current, cancellationToken).ConfigureAwait(false); + + return (group, key); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AnyOrElse.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AnyOrElse.cs new file mode 100644 index 000000000..7a964bd7f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AnyOrElse.cs @@ -0,0 +1,37 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Returns the items from when it has any or otherwise the items from . + [Pure] + public static IAsyncEnumerable AnyOrElse(this IAsyncEnumerable source, IAsyncEnumerable fallback) + => AnyOrElseInternal(source, () => fallback); + + /// Returns the items from when it has any or otherwise the items from . + [Pure] + public static IAsyncEnumerable AnyOrElse(this IAsyncEnumerable source, Func> fallback) + => AnyOrElseInternal(source, fallback); + + private static async IAsyncEnumerable AnyOrElseInternal(IAsyncEnumerable source, Func> fallback, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var hasItems = false; + + await foreach (var item in source.WithCancellation(cancellationToken)) + { + hasItems = true; + yield return item; + } + + if (!hasItems) + { + await foreach (var item in fallback().WithCancellation(cancellationToken)) + { + yield return item; + } + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNone.cs new file mode 100644 index 000000000..3a3b1367d --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNone.cs @@ -0,0 +1,294 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal.Aggregators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Computes the average of a sequence of values. If the sequence is empty it returns None. + /// + /// A sequence of values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence only consists of none or is empty it returns None. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values. If the sequence is empty it returns None. + /// + /// A sequence of values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence only consists of none or is empty it returns None. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values. If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// A sequence of values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values. If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// A sequence of values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence is empty it returns None. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values. If the sequence only consists of none or is empty it returns None. + /// + /// A sequence of optional values to determine the average value of. + /// A cancellation token. + /// The average value in the sequence or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, (calculator, element) => calculator.Add(element), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, (calculator, element) => calculator.Add(selector(element)), cancellationToken).ConfigureAwait(false)).Average; +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwait.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwait.cs new file mode 100644 index 000000000..8049bd097 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwait.cs @@ -0,0 +1,166 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal.Aggregators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, async (calculator, element, _) => calculator.Add(await selector(element).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwaitWithCancellation.cs b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwaitWithCancellation.cs new file mode 100644 index 000000000..d92ca9be4 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/AverageOrNoneAwaitWithCancellation.cs @@ -0,0 +1,166 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal.Aggregators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(FloatAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// The rules for floating point arithmetic apply see remarks for detail. + /// + /// + /// Any sequence containing at least one NaN value will evaluate to NaN. + /// A sequence containing at least one +Infinity will always evaluate to an average of +infinity. + /// A sequence containing at least one -Infinity will always evaluate to an average of -infinity. + /// A sequence containing at least one of each +Infinity and -Infinity values will always evaluate to NaN. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DoubleAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; + + /// + /// Computes the average of a sequence of optional values that are obtained by invoking a transform function on each element of the input sequence of . + /// If the sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of values that are used to calculate an average. + /// A transform function to apply to each element. + /// A cancellation token. + /// The average of the sequence of values or None. + [Pure] + public static async ValueTask> AverageOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + => (await source.AggregateAsync(DecimalAverageAggregator.Empty, async (calculator, element, token) => calculator.Add(await selector(element, token).ConfigureAwait(false)), cancellationToken).ConfigureAwait(false)).Average; +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/ConcatToString.cs b/Funcky/Extensions/AsyncEnumerableExtensions/ConcatToString.cs new file mode 100644 index 000000000..67c2dc434 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/ConcatToString.cs @@ -0,0 +1,23 @@ +#if INTEGRATED_ASYNC +using System.Text; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Concatenates the elements of the given sequence to a single string. + /// + /// Type of the elements in sequence. + /// Concatenated string. + [Pure] + public static async ValueTask ConcatToStringAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + { + var result = new StringBuilder(); + + await source.AggregateAsync(result, (builder, value) => builder.Append(value), cancellationToken).ConfigureAwait(false); + + return result.ToString(); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/ElementAtOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/ElementAtOrNone.cs new file mode 100644 index 000000000..b77ca3b9b --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/ElementAtOrNone.cs @@ -0,0 +1,63 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the element at a specified index in a sequence or an value if the index is out of range. + /// + /// The type of element contained by the sequence. + /// The sequence to find an element in. + /// The index for the element to retrieve. + /// Cancellation token. + /// The item at the specified index, or if the index is not found. + [Pure] + public static async ValueTask> ElementAtOrNoneAsync(this IAsyncEnumerable source, int index, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Select(Option.Some).ElementAtOrDefaultAsync(index, cancellationToken).ConfigureAwait(false); + +#if INDEX_TYPE + [Pure] + public static ValueTask> ElementAtOrNoneAsync(this IAsyncEnumerable source, Index index, CancellationToken cancellationToken = default) + where TSource : notnull + => index.IsFromEnd + ? ElementAtOrNoneFromEnd(source, index.Value, cancellationToken) + : source.ElementAtOrNoneAsync(index.Value, cancellationToken); + + // Adopted from: https://github.com/dotnet/runtime/blob/ebba1d4acb7abea5ba15e1f7f69d1d1311465d16/src/libraries/System.Linq/src/System/Linq/ElementAt.cs#L152-L183 + private static async ValueTask> ElementAtOrNoneFromEnd(IAsyncEnumerable source, int indexFromEnd, CancellationToken cancellationToken) + where TSource : notnull + { + if (indexFromEnd > 0) + { +#pragma warning disable CA2007 + await using var enumerator = source.ConfigureAwait(false).WithCancellation(cancellationToken).GetAsyncEnumerator(); +#pragma warning restore CA2007 + + if (await enumerator.MoveNextAsync()) + { + var queue = new Queue(); + queue.Enqueue(enumerator.Current); + + while (await enumerator.MoveNextAsync()) + { + if (queue.Count == indexFromEnd) + { + queue.Dequeue(); + } + + queue.Enqueue(enumerator.Current); + } + + if (queue.Count == indexFromEnd) + { + return queue.Dequeue(); + } + } + } + + return Option.None; + } +#endif +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/FirstOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/FirstOrNone.cs new file mode 100644 index 000000000..72ee76472 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/FirstOrNone.cs @@ -0,0 +1,34 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the first element of a sequence as an , or a value if the sequence contains no elements. + /// + [Pure] + public static async ValueTask> FirstOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Select(Option.Some).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + /// Returns the first element of the sequence as an that satisfies a condition or a value if no such element is found. + /// + [Pure] + public static async ValueTask> FirstOrNoneAsync(this IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> FirstOrNoneAwaitAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where((item, _) => predicate(item)).Select(Option.Some).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> FirstOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Inspect.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Inspect.cs new file mode 100644 index 000000000..dcaf48179 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Inspect.cs @@ -0,0 +1,83 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// An IEnumerable that calls a function on each element before yielding it. It can be used to encode side effects without enumerating. + /// The side effect will be executed whenever enumerating of the result happens. + /// + /// The inner type of the async enumerable. + /// An async enumerable. + /// A synchronous action. + /// Returns an with the side effect defined by action encoded in the enumerable. + [Pure] + public static IAsyncEnumerable Inspect(this IAsyncEnumerable source, Action inspector) + => InspectInternal(source, inspector); + + /// + /// An IEnumerable that calls and awaits the function on each element before yielding it. It can be used to encode side effects without enumerating. + /// The side effect will be executed whenever enumerating of the result happens. + /// + /// The inner type of the async enumerable. + /// An async enumerable. + /// An asynchronous action. + /// Returns an with the side effect defined by action encoded in the enumerable. + [Pure] + public static IAsyncEnumerable InspectAwait(this IAsyncEnumerable source, Func inspector) + => InspectAwaitInternal(source, inspector); + + /// + /// An IEnumerable that calls and awaits the function on each element before yielding it. It can be used to encode side effects without enumerating. + /// The side effect will be executed whenever enumerating of the result happens. + /// + /// The inner type of the async enumerable. + /// An async enumerable. + /// An asynchronous action. + /// Returns an with the side effect defined by action encoded in the enumerable. + [Pure] + public static IAsyncEnumerable InspectAwaitWithCancellation(this IAsyncEnumerable source, Func inspector) + => InspectAwaitWithCancellationInternal(source, inspector); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable InspectInternal( + this IAsyncEnumerable source, + Action action, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in source.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + action(item); + yield return item; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable InspectAwaitInternal( + this IAsyncEnumerable source, + Func action, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in source.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + await action(item).ConfigureAwait(false); + yield return item; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable InspectAwaitWithCancellationInternal( + this IAsyncEnumerable source, + Func action, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in source.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + await action(item, cancellationToken).ConfigureAwait(false); + yield return item; + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/InspectEmpty.cs b/Funcky/Extensions/AsyncEnumerableExtensions/InspectEmpty.cs new file mode 100644 index 000000000..0fd83348e --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/InspectEmpty.cs @@ -0,0 +1,43 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// An IAsyncEnumerable that calls a function if and only if the source has no element to enumerate. It can be used to encode side effects on an empty IAsyncEnumerable. + /// The side effect will be executed when enumerating the result. + /// + /// the inner type of the enumerable. + /// returns an with the side effect defined by action encoded in the async enumerable. + [Pure] + public static IAsyncEnumerable InspectEmpty(this IAsyncEnumerable source, Action inspector) + => InspectEmptyInternal(source, inspector); + + private static async IAsyncEnumerable InspectEmptyInternal( + this IAsyncEnumerable source, + Action inspector, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using var enumerator = source.ConfigureAwait(false).WithCancellation(cancellationToken).GetAsyncEnumerator(); +#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + + if (await enumerator.MoveNextAsync()) + { + yield return enumerator.Current; + } + else + { + inspector(); + yield break; + } + + while (await enumerator.MoveNextAsync()) + { + yield return enumerator.Current; + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Interleave.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Interleave.cs new file mode 100644 index 000000000..627e8e99f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Interleave.cs @@ -0,0 +1,73 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Interleaves the elements of multiple sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completely consumed. + /// + /// The type of the elements in the source sequences. + /// first sequence. + /// other sequences. + /// one sequences with all the elements interleaved. + [Pure] + public static IAsyncEnumerable Interleave(this IAsyncEnumerable source, params IAsyncEnumerable[] otherSources) + => ImmutableList.Create(source).AddRange(otherSources).Interleave(); + + /// + /// Interleaves the elements of a sequence of sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completely consumed. + /// + /// The type of the elements in the source sequences. + /// the source sequences. + /// one sequences with all the elements interleaved. + [Pure] + public static IAsyncEnumerable Interleave(this IEnumerable> source) => InterleaveInternal(source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable InterleaveInternal( + IEnumerable> source, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerators = GetInterleaveEnumerators(source, cancellationToken); + + try + { + await foreach (var element in InterleaveEnumeratorAsync(enumerators, cancellationToken).ConfigureAwait(false)) + { + yield return element; + } + } + finally + { + await foreach (var enumerator in enumerators.ToAsyncEnumerable().WithCancellation(cancellationToken).ConfigureAwait(false)) + { + await DisposeEnumerator(enumerator).ConfigureAwait(false); + } + } + } + +#pragma warning disable IDISP007 // The entire point of this method is to dispose. + private static async Task DisposeEnumerator(IAsyncEnumerator enumerator) => await enumerator.DisposeAsync().ConfigureAwait(false); +#pragma warning restore IDISP007 + + private static ImmutableList> GetInterleaveEnumerators( + IEnumerable> source, + CancellationToken cancellationToken) + => source.Select(s => s.GetAsyncEnumerator(cancellationToken)).ToImmutableList(); + + private static async IAsyncEnumerable InterleaveEnumeratorAsync(ImmutableList> enumerators, [EnumeratorCancellation] CancellationToken cancellationToken) + { + while (!enumerators.IsEmpty) + { + enumerators = enumerators.RemoveRange(await enumerators.ToAsyncEnumerable().Where(async (f, _) => await HasMoreElements(f).ConfigureAwait(false)).ToListAsync(cancellationToken).ConfigureAwait(false)); + foreach (var enumerator in enumerators) + { + yield return enumerator.Current; + } + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Intersperse.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Intersperse.cs new file mode 100644 index 000000000..1d4dc7529 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Intersperse.cs @@ -0,0 +1,14 @@ +#if INTEGRATED_ASYNC + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Returns a sequence with the items of the source sequence interspersed with the given . + [Pure] + public static IAsyncEnumerable Intersperse(this IAsyncEnumerable source, TSource element) + => source.WithFirst().SelectMany(item => item.IsFirst + ? AsyncSequence.Return(item.Value) + : AsyncSequence.Return(element).Append(item.Value)); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/JoinToString.cs b/Funcky/Extensions/AsyncEnumerableExtensions/JoinToString.cs new file mode 100644 index 000000000..2e2672c4d --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/JoinToString.cs @@ -0,0 +1,51 @@ +#if INTEGRATED_ASYNC +using System.Text; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Concatenates the elements of the given sequence, using the specified separator between each element or member. + /// + /// Type of the elements in sequence. + /// A sequence of items to be joined in a string. + /// A single character to separate the individual elements. + /// A cancellation token. + /// Joined string with separators between the elements. + [Pure] + public static ValueTask JoinToStringAsync(this IAsyncEnumerable source, char separator, CancellationToken cancellationToken = default) + => JoinToStringInternal(separator.ToString(), source, cancellationToken); + + /// + /// Concatenates the elements of the given sequence, using the specified separator between each element or member. + /// + /// Type of the elements in sequence. + /// A sequence of items to be joined in a string. + /// A string to separate the individual elements. + /// A cancellation token. + /// Joined string with separators between the elements. + [Pure] + public static ValueTask JoinToStringAsync(this IAsyncEnumerable source, string separator, CancellationToken cancellationToken = default) + => JoinToStringInternal(separator, source, cancellationToken); + + private static async ValueTask JoinToStringInternal(string separator, IAsyncEnumerable values, CancellationToken cancellationToken = default) + { + var result = new StringBuilder(); + var enumerator = values.GetAsyncEnumerator(cancellationToken); + + if (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + result.Append(enumerator.Current); + } + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + result.Append(separator); + result.Append(enumerator.Current); + } + + return result.ToString(); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/LastOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/LastOrNone.cs new file mode 100644 index 000000000..14eb79db7 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/LastOrNone.cs @@ -0,0 +1,34 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the last element of a sequence as an , or a value if the sequence contains no elements. + /// + [Pure] + public static async ValueTask> LastOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Select(Option.Some).LastOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + /// Returns the last element of the sequence as an that satisfies a condition or a value if no such element is found. + /// + [Pure] + public static async ValueTask> LastOrNoneAsync(this IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).LastOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> LastOrNoneAwaitAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where((item, _) => predicate(item)).Select(Option.Some).LastOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> LastOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).LastOrDefaultAsync(cancellationToken).ConfigureAwait(false); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Materialize.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Materialize.cs new file mode 100644 index 000000000..ca43f057e --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Materialize.cs @@ -0,0 +1,39 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Materializes all the items of a lazy . If the underlying sequence is a collection type we do not actively enumerate them. + /// + /// Type of the items in the source sequence. + /// The source sequence can be any . + /// A cancellation token. + /// A collection of the enumerated items. + public static async ValueTask> MaterializeAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => await source.MaterializeAsync(DefaultMaterializerAsync, cancellationToken).ConfigureAwait(false); + + /// + /// Materializes all the items of a lazy . If the underlying sequence is a collection type we do not actively enumerate them. + /// Via the materialize function you can chose how the enumeration is done when it is needed. + /// + /// Type of the items in the source sequence. + /// The type of the materialization target. + /// The source sequence can be any . + /// A function which materializes a given sequence into a collection. + /// A cancellation token. + /// A collection of the enumerated items. + public static async ValueTask> MaterializeAsync( + this IAsyncEnumerable source, + Func, CancellationToken, ValueTask> materializer, + CancellationToken cancellationToken = default) + where TMaterialization : IReadOnlyCollection + => source switch + { + _ => await materializer(source, cancellationToken).ConfigureAwait(false), + }; + + private static async ValueTask> DefaultMaterializerAsync(IAsyncEnumerable source, CancellationToken cancellationToken = default) + => await source.ToListAsync(cancellationToken).ConfigureAwait(false); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/MaxOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/MaxOrNone.cs new file mode 100644 index 000000000..fa4e50d25 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/MaxOrNone.cs @@ -0,0 +1,115 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal.Aggregators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the maximum value in a sequence of generic values compared by a . If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + where TSource : notnull + => source.MaxOrNoneAsync(Identity, cancellationToken); + + /// + /// Returns the maximum value in a sequence of optional generic values compared by a . If the sequence only consists of none or is empty it returns None. + /// + /// A sequence of optional generic values of TSource to determine the maximum value of. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + where TSource : notnull + => source.WhereSelect().MaxOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, MaxAggregator.Aggregate, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, async (min, current, _) => MaxAggregator.Aggregate(min, await current.ConfigureAwait(false)), cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, MaxAggregator.Aggregate, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the optional generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelect(selector).MaxOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the optional generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelectAwait(selector).MaxOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum from the optional generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of TSource to determine the maximum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The maximum value in the sequence or None. + [Pure] + public static ValueTask> MaxOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelectAwaitWithCancellation(selector).MaxOrNoneAsync(Identity, cancellationToken); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Memoize.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Memoize.cs new file mode 100644 index 000000000..bbe73d69a --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Memoize.cs @@ -0,0 +1,109 @@ +#if INTEGRATED_ASYNC +#pragma warning disable SA1010 // StyleCop support for collection expressions is missing +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Creates a buffer with a view over the source sequence, causing each enumerator to obtain access to all of the + /// sequence's elements without causing multiple enumerations over the source. + /// + /// Type of the elements in sequence. + /// The source sequence. + /// A lazy buffer of the underlying sequence. + [Pure] + public static IAsyncBuffer Memoize(this IAsyncEnumerable source) + => source is IAsyncBuffer buffer + ? Borrow(buffer) + : MemoizedAsyncBuffer.Create(source); + + private static IAsyncBuffer Borrow(IAsyncBuffer buffer) + => new BorrowedAsyncBuffer(buffer); + + private static class MemoizedAsyncBuffer + { + public static MemoizedAsyncBuffer Create(IAsyncEnumerable source) + => new(source); + } + + private sealed class BorrowedAsyncBuffer(IAsyncBuffer inner) : IAsyncBuffer + { + private bool _disposed; + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + return inner.GetAsyncEnumerator(cancellationToken); + } + + public ValueTask DisposeAsync() + { + _disposed = true; + return default; + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(BorrowedAsyncBuffer)); + } + } + } + + private sealed class MemoizedAsyncBuffer(IAsyncEnumerable source) : IAsyncBuffer + { + private readonly List _buffer = []; + private readonly IAsyncEnumerator _source = source.GetAsyncEnumerator(); + + private bool _disposed; + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + + return GetAsyncEnumeratorInternal(); + } + + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await _source.DisposeAsync().ConfigureAwait(false); + _buffer.Clear(); + _disposed = true; + } + } + + private async IAsyncEnumerator GetAsyncEnumeratorInternal() + { + for (var index = 0; true; index++) + { + ThrowIfDisposed(); + + if (index == _buffer.Count) + { + if (await _source.MoveNextAsync().ConfigureAwait(false)) + { + _buffer.Add(_source.Current); + } + else + { + break; + } + } + + yield return _buffer[index]; + } + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(MemoizedAsyncBuffer)); + } + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Merge.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Merge.cs new file mode 100644 index 000000000..924306962 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Merge.cs @@ -0,0 +1,110 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Merges two ordered sequences into one and preserves the ordering. The merged sequences has exactly the same number of elements as the inputs combined. + /// + /// PRECONDITION: The given sequences must be ordered by the same ordering as the given IComparer. + /// First ordered sequence. + /// Second ordered sequence. + /// The comparer giving the order condition of the sequences. + /// The type elements in the sequences. + /// The merged sequences in the same order as the given sequences. + [Pure] + public static IAsyncEnumerable Merge(this IAsyncEnumerable source1, IAsyncEnumerable source2, Option> comparer = default) + => ImmutableList.Create(source1, source2).Merge(comparer); + + /// + /// Merges three ordered sequences into one and preserves the ordering. The merged sequences has exactly the same number of elements as the inputs combined. + /// + /// PRECONDITION: The given sequences must be ordered by the same ordering as the given IComparer. + /// First ordered sequence. + /// Second ordered sequence. + /// Third ordered sequence. + /// The comparer giving the order condition of the sequences. + /// The type elements in the sequences. + /// The merged sequences in the same order as the given sequences. + [Pure] + public static IAsyncEnumerable Merge(this IAsyncEnumerable source1, IAsyncEnumerable source2, IAsyncEnumerable source3, Option> comparer = default) + => ImmutableList.Create(source1, source2, source3).Merge(comparer); + + /// + /// Merges three ordered sequences into one and preserves the ordering. The merged sequences has exactly the same number of elements as the inputs combined. + /// + /// PRECONDITION: The given sequences must be ordered by the same ordering as the given IComparer. + /// First ordered sequence. + /// Second ordered sequence. + /// Third ordered sequence. + /// Forth ordered sequence. + /// The comparer giving the order condition of the sequences. + /// The type elements in the sequences. + /// The merged sequences in the same order as the given sequences. + [Pure] + public static IAsyncEnumerable Merge(this IAsyncEnumerable source1, IAsyncEnumerable source2, IAsyncEnumerable source3, IAsyncEnumerable source4, Option> comparer = default) + => ImmutableList.Create(source1, source2, source3, source4).Merge(comparer); + + /// + /// Merges a sequence of ordered sequences into one and preserves the ordering. The merged sequences has exactly the same number of elements as the inputs combined. + /// + /// PRECONDITION: The given sequences must be ordered by the same ordering as the given IComparer. + /// First ordered sequence. + /// The comparer giving the order condition of the sequences. + /// The type elements in the sequences. + /// The merged sequences in the same order as the given sequences. + [Pure] + public static async IAsyncEnumerable Merge(this IEnumerable> sources, Option> comparer = default) + { + var enumerators = GetMergeEnumerators(sources); + + try + { + await foreach (var element in MergeEnumerators(enumerators.RemoveRange(await enumerators.ToAsyncEnumerable().Where(async (f, _) => await HasMoreElements(f).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false)), GetMergeComparer(comparer)).ConfigureAwait(false)) + { + yield return element; + } + } + finally + { + foreach (var enumerator in enumerators) + { + await enumerator.DisposeAsync().ConfigureAwait(false); + } + } + } + + private static ImmutableList> GetMergeEnumerators(IEnumerable> sources) + => ImmutableList.Create>().AddRange(sources.Select(s => s.GetAsyncEnumerator())); + + private static IComparer GetMergeComparer(Option> comparer = default) + => comparer.GetOrElse(Comparer.Default); + + private static async Task HasMoreElements(IAsyncEnumerator enumerator) + => !await enumerator.MoveNextAsync().ConfigureAwait(false); + + private static async IAsyncEnumerable MergeEnumerators(ImmutableList> enumerators, IComparer comparer) + { + while (!enumerators.IsEmpty) + { + var minimum = FindMinimum(enumerators, comparer); + yield return minimum.Current; + + enumerators = await RemoveYieldedAsync(minimum, enumerators).ConfigureAwait(false); + } + } + + private static async Task>> RemoveYieldedAsync(IAsyncEnumerator minimum, ImmutableList> enumerators) + => await minimum.MoveNextAsync().ConfigureAwait(false) + ? enumerators + : enumerators.Remove(minimum); + + private static IAsyncEnumerator FindMinimum(ImmutableList> enumerators, IComparer comparer) + => enumerators.Aggregate(Minimum(comparer)); + + private static Func, IAsyncEnumerator, IAsyncEnumerator> Minimum(IComparer comparer) + => (enumerator, minimum) => comparer.Compare(minimum.Current, enumerator.Current) <= 0 ? minimum : enumerator; +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/MinOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/MinOrNone.cs new file mode 100644 index 000000000..89644782f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/MinOrNone.cs @@ -0,0 +1,115 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal.Aggregators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the minimum value in a sequence of generic values compared by a . If the sequence is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + where TSource : notnull + => source.MinOrNoneAsync(Identity, cancellationToken); + + /// + /// Returns the minimum value in a sequence of optional generic values compared by a . If the sequence only consists of none or is empty it returns None. + /// + /// A sequence of optional generic values of to determine the minimum value of. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAsync(this IAsyncEnumerable> source, CancellationToken cancellationToken = default) + where TSource : notnull + => source.WhereSelect().MinOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAsync(this IAsyncEnumerable source, Func selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, MinAggregator.Aggregate, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAwaitAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, async (min, current, _) => MinAggregator.Aggregate(min, await current.ConfigureAwait(false)), cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.Select(selector).AggregateAsync(Option.None, MinAggregator.Aggregate, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the optional generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAsync(this IAsyncEnumerable source, Func> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelect(selector).MinOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAwaitAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelectAwait(selector).MinOrNoneAsync(Identity, cancellationToken); + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum from the optional generic values compared by a . If the transformed sequence only consists of none or is empty it returns None. + /// + /// The type of the elements of source. + /// A sequence of generic values of to determine the minimum value of. + /// The type of the value returned by selector. + /// A transform function to apply to each element. + /// A cancellation token. + /// The minimum value in the sequence or None. + [Pure] + public static ValueTask> MinOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func>> selector, CancellationToken cancellationToken = default) + where TResult : notnull + => source.WhereSelectAwaitWithCancellation(selector).MinOrNoneAsync(Identity, cancellationToken); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/None.cs b/Funcky/Extensions/AsyncEnumerableExtensions/None.cs new file mode 100644 index 000000000..bf837fb56 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/None.cs @@ -0,0 +1,26 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Determines whether a sequence contains no elements. + [Pure] + public static async ValueTask NoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => !await source.AnyAsync(cancellationToken).ConfigureAwait(false); + + /// Determines whether no element exists or satisfies a condition. + [Pure] + public static async ValueTask NoneAsync(this IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken = default) + => !await source.AnyAsync(predicate, cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask NoneAwaitAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + => !await source.AnyAsync((item, _) => predicate(item), cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask NoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + => !await source.AnyAsync(predicate, cancellationToken).ConfigureAwait(false); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Pairwise.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Pairwise.cs new file mode 100644 index 000000000..05db83e44 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Pairwise.cs @@ -0,0 +1,52 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns a sequence of pair-wise tuples from the underlying sequence. + /// + /// The source sequence. + /// Type of the elements in sequence. + /// Returns a sequence of ValueTuple-pairs. + [Pure] + public static IAsyncEnumerable<(TSource Front, TSource Back)> Pairwise(this IAsyncEnumerable source) + => Pairwise(source, ValueTuple.Create); + + /// + /// Applies a function to the element and its successor pairwise from the underlying sequence and returns a new sequence with these results. + /// I.e. the resulting sequence has one element less then source sequence. + /// + /// The source sequence. + /// A function of type (TSource, TSource) -> TResult which creates the results from the pairs. + /// Type of the elements in sequence. + /// The type of the elements in the result Sequence. + /// Returns a sequence of ValueTuple-pairs. + [Pure] + public static IAsyncEnumerable Pairwise(this IAsyncEnumerable source, Func resultSelector) + => PairwiseInternal(source, resultSelector); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable PairwiseInternal( + this IAsyncEnumerable source, + Func resultSelector, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using var enumerator = source.ConfigureAwait(false).WithCancellation(cancellationToken).GetAsyncEnumerator(); + #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + + if (await enumerator.MoveNextAsync() == false) + { + yield break; + } + + for (var previous = enumerator.Current; await enumerator.MoveNextAsync(); previous = enumerator.Current) + { + yield return resultSelector(previous, enumerator.Current); + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Partition.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Partition.cs new file mode 100644 index 000000000..e0f406b9c --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Partition.cs @@ -0,0 +1,96 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal; +using static Funcky.ValueTaskFactory; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Partitions the items in an by the given . + /// + /// n % 2 == 0); + /// ]]> + /// + /// + /// This method causes the items in to be materialized. + /// A tuple with the items for which the predicate holds, and for those for which it doesn't. + public static ValueTask> PartitionAsync( + this IAsyncEnumerable source, + Func predicate, + CancellationToken cancellationToken = default) + => source.PartitionAsync(predicate, Partitions.Create, cancellationToken); + + /// + public static ValueTask> PartitionAwaitAsync( + this IAsyncEnumerable source, + Func> predicate, + CancellationToken cancellationToken = default) + => source.PartitionAwaitAsync(predicate, static (left, right) => ValueTaskFromResult(Partitions.Create(left, right)), cancellationToken); + + /// + public static ValueTask> PartitionAwaitWithCancellationAsync( + this IAsyncEnumerable source, + Func> predicate, + CancellationToken cancellationToken = default) + => source.PartitionAwaitWithCancellationAsync(predicate, static (left, right, _) => ValueTaskFromResult(Partitions.Create(left, right)), cancellationToken); + + /// + /// Partitions the items in an by the given . + /// The receives the items for which the predicate holds and the items + /// for which it doesn't as separate parameters. + /// + /// This method causes the items in to be materialized. + public static async ValueTask PartitionAsync( + this IAsyncEnumerable source, + Func predicate, + Func, IReadOnlyList, TResult> resultSelector, + CancellationToken cancellationToken = default) + => (await source + .AggregateAsync(new PartitionBuilder(), PartitionBuilder.Add(predicate), cancellationToken) + .ConfigureAwait(false)) + .Build(resultSelector); + + /// + public static async ValueTask PartitionAwaitAsync( + this IAsyncEnumerable source, + Func> predicate, + Func, IReadOnlyList, ValueTask> resultSelector, + CancellationToken cancellationToken = default) + { + var (left, right) = + (await source + .AggregateAsync(new PartitionBuilder(), (total, current, _) => AddAwaitAsync(predicate)(total, current), cancellationToken) + .ConfigureAwait(false)) + .Build(ValueTuple.Create); + return await resultSelector(left, right).ConfigureAwait(false); + } + + /// + public static async ValueTask PartitionAwaitWithCancellationAsync( + this IAsyncEnumerable source, + Func> predicate, + Func, IReadOnlyList, CancellationToken, ValueTask> resultSelector, + CancellationToken cancellationToken = default) + { + var (left, right) = + (await source + .AggregateAsync(new PartitionBuilder(), AddAwaitWithCancellationAsync(predicate), cancellationToken) + .ConfigureAwait(false)) + .Build(ValueTuple.Create); + return await resultSelector(left, right, cancellationToken).ConfigureAwait(false); + } + + private static Func, TSource, ValueTask>> AddAwaitAsync( + Func> predicate) + => async (builder, element) => await predicate(element).ConfigureAwait(false) ? builder.AddLeft(element) : builder.AddRight(element); + + private static Func, TSource, CancellationToken, ValueTask>> AddAwaitWithCancellationAsync( + Func> predicate) + => async (builder, element, cancellationToken) => await predicate(element, cancellationToken).ConfigureAwait(false) ? builder.AddLeft(element) : builder.AddRight(element); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/PartitionEither.cs b/Funcky/Extensions/AsyncEnumerableExtensions/PartitionEither.cs new file mode 100644 index 000000000..aa736b02f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/PartitionEither.cs @@ -0,0 +1,47 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Partitions the either values in an into a left and a right partition. + public static ValueTask> PartitionAsync( + this IAsyncEnumerable> source, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TRight : notnull + => source.PartitionAsync(EitherPartitions.Create, cancellationToken); + + /// + public static async ValueTask PartitionAsync( + this IAsyncEnumerable> source, + Func, IReadOnlyList, TResult> resultSelector, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TRight : notnull + => (await source + .AggregateAsync(new PartitionBuilder(), PartitionBuilder.Add, cancellationToken) + .ConfigureAwait(false)) + .Build(resultSelector); + + /// Partitions the items in an into a left and a right partition. + public static ValueTask> PartitionAsync( + this IAsyncEnumerable source, + Func> selector, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TRight : notnull + => source.Select(selector).PartitionAsync(EitherPartitions.Create, cancellationToken); + + /// + public static ValueTask PartitionAsync( + this IAsyncEnumerable source, + Func> selector, + Func, IReadOnlyList, TResult> resultSelector, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TRight : notnull + => source.Select(selector).PartitionAsync(resultSelector, cancellationToken); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/PartitionResult.cs b/Funcky/Extensions/AsyncEnumerableExtensions/PartitionResult.cs new file mode 100644 index 000000000..e1a073fb2 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/PartitionResult.cs @@ -0,0 +1,26 @@ +#if INTEGRATED_ASYNC +using Funcky.Internal; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Partitions the result values in an into an error and ok partition. + public static ValueTask> PartitionAsync( + this IAsyncEnumerable> source, + CancellationToken cancellationToken = default) + where TValidResult : notnull + => source.PartitionAsync(ResultPartitions.Create, cancellationToken); + + /// Partitions the either values in an into an error and ok partition. + public static async ValueTask PartitionAsync( + this IAsyncEnumerable> source, + Func, IReadOnlyList, TResult> resultSelector, + CancellationToken cancellationToken = default) + where TValidResult : notnull + => (await source + .AggregateAsync(new PartitionBuilder(), PartitionBuilder.Add, cancellationToken) + .ConfigureAwait(false)) + .Build(resultSelector); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/PowerSet.cs b/Funcky/Extensions/AsyncEnumerableExtensions/PowerSet.cs new file mode 100644 index 000000000..d1e386369 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/PowerSet.cs @@ -0,0 +1,49 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// The PowerSet function returns a sequence with the set of all subsets. + /// + /// The type of the elements in the enumerable. + /// The source sequence. + /// Returns an sequence which includes all subsets of the given sequence. + /// The PowerSet function returns a sequence with 2^n elements where n is the number of elements int the source sequence. + /// This means it is only viable for small source sequences. + public static IAsyncEnumerable> PowerSet(this IAsyncEnumerable source) + => source.PowerSetInternal(); + + private static async IAsyncEnumerable> PowerSetInternal(this IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { +#pragma warning disable CA2007 // Configured via IAsyncEnumerable extension + await using var asyncEnumerator = source.ConfigureAwait(false).WithCancellation(cancellationToken).GetAsyncEnumerator(); +#pragma warning restore CA2007 + + await foreach (var set in PowerSetEnumerator(asyncEnumerator).WithCancellation(cancellationToken).ConfigureAwait(false)) + { + yield return set; + } + } + + private static async IAsyncEnumerable> PowerSetEnumerator(this ConfiguredCancelableAsyncEnumerable.Enumerator source) + { + if (await source.MoveNextAsync()) + { + var temp = source.Current; + await foreach (var set in source.PowerSetEnumerator().ConfigureAwait(false)) + { + yield return set; + yield return set.Push(temp); + } + } + else + { + yield return ImmutableStack.Empty; + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Scan.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Scan.cs new file mode 100644 index 000000000..da30e48f6 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Scan.cs @@ -0,0 +1,152 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Scan generates a sequence known as the inclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// A binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable InclusiveScan(this IAsyncEnumerable source, TAccumulate seed, Func accumulator) + => InclusiveScanEnumerable(source, seed, accumulator); + + /// + /// Scan generates a sequence known as the inclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// An awaitable binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable InclusiveScanAwait(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator) + => InclusiveScanAwaitEnumerable(source, seed, accumulator); + + /// + /// Scan generates a sequence known as the inclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// A binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable InclusiveScanAwaitWithCancellation(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator) + => InclusiveScanAwaitWithCancellationEnumerable(source, seed, accumulator); + + /// + /// Scan generates a sequence known as the exclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// a binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable ExclusiveScan(this IAsyncEnumerable source, TAccumulate seed, Func accumulator) + => ExclusiveScanEnumerable(source, seed, accumulator); + + /// + /// Scan generates a sequence known as the exclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// An awaitable binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable ExclusiveScanAwait(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator) + => ExclusiveScanAwaitEnumerable(source, seed, accumulator); + + /// + /// Scan generates a sequence known as the exclusive prefix sum. + /// + /// The type of the source elements. + /// The seed and target type. + /// The source sequence. + /// The seed or neutral element (identity). + /// An awaitable binary operator to aggregate a value. + /// A sequence of aggregated values. + public static IAsyncEnumerable ExclusiveScanAwaitWithCancellation(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator) + => ExclusiveScanAwaitWithCancellationEnumerable(source, seed, accumulator); + + private static async IAsyncEnumerable InclusiveScanEnumerable(IAsyncEnumerable source, TAccumulate seed, Func accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + seed = accumulator(seed, enumerator.Current); + yield return seed; + } + } + + private static async IAsyncEnumerable InclusiveScanAwaitEnumerable(IAsyncEnumerable source, TAccumulate seed, Func> accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + seed = await accumulator(seed, enumerator.Current).ConfigureAwait(false); + yield return seed; + } + } + + private static async IAsyncEnumerable InclusiveScanAwaitWithCancellationEnumerable(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + seed = await accumulator(seed, enumerator.Current, cancellationToken).ConfigureAwait(false); + yield return seed; + } + } + + private static async IAsyncEnumerable ExclusiveScanEnumerable(this IAsyncEnumerable source, TAccumulate seed, Func accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield return seed; + seed = accumulator(seed, enumerator.Current); + } + } + + private static async IAsyncEnumerable ExclusiveScanAwaitEnumerable(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield return seed; + seed = await accumulator(seed, enumerator.Current).ConfigureAwait(false); + } + } + + private static async IAsyncEnumerable ExclusiveScanAwaitWithCancellationEnumerable(this IAsyncEnumerable source, TAccumulate seed, Func> accumulator, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var enumerator = source.GetAsyncEnumerator(cancellationToken); + await using var enumeratorGuard = enumerator.ConfigureAwait(false); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield return seed; + seed = await accumulator(seed, enumerator.Current, cancellationToken).ConfigureAwait(false); + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Sequence.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Sequence.cs new file mode 100644 index 000000000..019e17a67 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Sequence.cs @@ -0,0 +1,70 @@ +#if INTEGRATED_ASYNC +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Funcky.Internal; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + public static async ValueTask>> SequenceAsync( + this IAsyncEnumerable> source, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TSource : notnull + => (await source.TraverseAsync(UnsafeEither.FromEither, cancellationToken).ConfigureAwait(false)).ToEither(); + + public static async ValueTask>> SequenceAsync( + this IAsyncEnumerable> source, + CancellationToken cancellationToken = default) + where TSource : notnull + => (await source.TraverseAsync(UnsafeEither.FromOption, cancellationToken).ConfigureAwait(false)).ToOption(); + + public static async ValueTask>> SequenceAsync( + this IAsyncEnumerable> source, + CancellationToken cancellationToken = default) + where TSource : notnull + => (await source.TraverseAsync(UnsafeEither.FromResult, cancellationToken).ConfigureAwait(false)).ToResult(); + + [Pure] + public static Reader> Sequence(this IAsyncEnumerable> source) + where TEnvironment : notnull + where TSource : notnull + => environment + => source.Select(reader => reader(environment)); + + [Pure] + public static Lazy> Sequence<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] TSource>(this IAsyncEnumerable> source) + => Lazy.FromFunc(new SequenceLazyInternal(source).Invoke); + + private static async ValueTask>> TraverseAsync( + this IAsyncEnumerable source, + Func> selector, + CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); + + await foreach (var element in source.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + var either = selector(element); + + if (!either.IsRight) + { + return UnsafeEither>.Left(either.LeftValue); + } + + builder.Add(either.RightValue); + } + + return UnsafeEither>.Right(builder.ToImmutable()); + } + + private sealed class SequenceLazyInternal<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] TSource>( + IAsyncEnumerable> source) + { + // Workaround for https://github.com/dotnet/linker/issues/1416 + public IAsyncEnumerable Invoke() => source.Select(lazy => lazy.Value); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Shuffle.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Shuffle.cs new file mode 100644 index 000000000..06c650d9b --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Shuffle.cs @@ -0,0 +1,31 @@ +#if INTEGRATED_ASYNC +#if !RANDOM_SHUFFLE +using Funcky.Internal; +#endif + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the given sequence eagerly in random Order in O(n). + /// + /// The type of the elements in the enumerable. + /// This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach. + [Pure] + public static async ValueTask> ShuffleAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + => await source.ShuffleAsync(new Random(), cancellationToken).ConfigureAwait(false); + + [Pure] + public static async ValueTask> ShuffleAsync(this IAsyncEnumerable source, Random random, CancellationToken cancellationToken = default) +#if RANDOM_SHUFFLE + { + var materialized = await source.ToArrayAsync(cancellationToken).ConfigureAwait(false); + random.Shuffle(materialized); + return materialized; + } +#else + => (await source.ToListAsync(cancellationToken).ConfigureAwait(false)).ToRandomList(random); +#endif +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/SingleOrNone.cs b/Funcky/Extensions/AsyncEnumerableExtensions/SingleOrNone.cs new file mode 100644 index 000000000..d32f427cb --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/SingleOrNone.cs @@ -0,0 +1,36 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns the only element of a sequence as an , or a value if the sequence is empty. + /// + /// Thrown if there is more than one element in the sequence. + [Pure] + public static async ValueTask> SingleOrNoneAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Select(Option.Some).SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + /// Returns the only element of a sequence that satisfies a specified condition as an or a value if no such element exists. + /// + /// Thrown if more than one element satisfies the condition. + [Pure] + public static async ValueTask> SingleOrNoneAsync(this IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> SingleOrNoneAwaitAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where((item, _) => predicate(item)).Select(Option.Some).SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + /// + [Pure] + public static async ValueTask> SingleOrNoneAwaitWithCancellationAsync(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default) + where TSource : notnull + => await source.Where(predicate).Select(Option.Some).SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs b/Funcky/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs new file mode 100644 index 000000000..267c257ae --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/SlidingWindow.cs @@ -0,0 +1,42 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; +using Funcky.Internal; +using Funcky.Internal.Validators; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// SlidingWindow returns a sequence of sliding windows of the given width. + /// The nth sequence will start with the nth element of the source sequence. + /// + /// + /// The returned windows always have many elements. + /// i.e. if your source sequence is smaller than the window, there will be an empty result. + /// + /// The source sequence. + /// The width of the sliding window. + /// The type of the source elements. + /// Returns a sequence of equally sized window sequences. + [Pure] + public static IAsyncEnumerable> SlidingWindow(this IAsyncEnumerable source, int width) + => SlidingWindowEnumerable(source, WindowWidthValidator.Validate(width)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async IAsyncEnumerable> SlidingWindowEnumerable( + IAsyncEnumerable source, + int width, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var slidingWindow = new SlidingWindowQueue(width); + await foreach (var element in source.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (slidingWindow.Enqueue(element).IsFull) + { + yield return slidingWindow.Window; + } + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Split.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Split.cs new file mode 100644 index 000000000..f8f06ccf4 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Split.cs @@ -0,0 +1,83 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Splits the source sequence by the given separator. + /// + /// Type of the elements in sequence. + /// The source sequence. + /// A single element of type separating the parts. + /// A sequence of sequences. + [Pure] + public static IAsyncEnumerable> Split(this IAsyncEnumerable source, TSource separator) + => SplitEnumerator(source, separator, EqualityComparer.Default); + + /// + /// Splits the source sequence by the given separator and the given equality. + /// + /// Type of the elements in sequence. + /// The source sequence. + /// A single element of type separating the parts. + /// Override the default equality comparer. + /// A sequence of sequences. + [Pure] + public static IAsyncEnumerable> Split(this IAsyncEnumerable source, TSource separator, IEqualityComparer comparer) + => SplitEnumerator(source, separator, comparer); + + /// + /// Splits the source sequence by the given separator and the given equality. + /// With the resultSelector you can chose what to construct from the individual part. + /// + /// Type of the elements in sequence. + /// Type of the elements produced by the . + /// The source sequence. + /// A single element of type separating the parts. + /// Override the default equality comparer. + /// The result selector produces a result from each partial sequence. + /// A sequence of results. + [Pure] + public static IAsyncEnumerable Split( + this IAsyncEnumerable source, + TSource separator, + IEqualityComparer comparer, + Func, TResult> resultSelector) + where TSource : notnull + => SplitEnumerator(source, separator, comparer) + .Select(resultSelector); + + private static async IAsyncEnumerable> SplitEnumerator(IAsyncEnumerable source, TSource separator, IEqualityComparer comparer, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken); + await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false); + + while (await asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield return await TakeSkipWhile(asyncEnumerator, TakeSkipPredicate(separator, comparer)).ToListAsync(cancellationToken).ConfigureAwait(false); + } + } + + private static Func TakeSkipPredicate(TSource separator, IEqualityComparer comparer) + => element + => !comparer.Equals(element, separator); + + private static async IAsyncEnumerable TakeSkipWhile(IAsyncEnumerator source, Func predicate) + { + do + { + if (predicate(source.Current)) + { + yield return source.Current; + } + else + { + yield break; + } + } + while (await source.MoveNextAsync().ConfigureAwait(false)); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/TakeEvery.cs b/Funcky/Extensions/AsyncEnumerableExtensions/TakeEvery.cs new file mode 100644 index 000000000..0b8408c5f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/TakeEvery.cs @@ -0,0 +1,29 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns every nth (interval) element from the source sequence. + /// + /// The type of the elements in the source sequence. + /// The source sequence. + /// The interval between elements in the source sequences. + /// Returns a sequence where only every n'th element (interval) of source sequence is used. + [Pure] + public static IAsyncEnumerable TakeEvery(this IAsyncEnumerable source, int interval) + { + ValidateInterval(interval); + + return source.Where((_, index) => index % interval == 0); + } + + private static void ValidateInterval(int interval) + { + if (interval <= 0) + { + throw new ArgumentOutOfRangeException(nameof(interval), interval, "Interval must be bigger than 0"); + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Transpose.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Transpose.cs new file mode 100644 index 000000000..669d6202c --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Transpose.cs @@ -0,0 +1,23 @@ +#if INTEGRATED_ASYNC +using System.Diagnostics.CodeAnalysis; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// On a rectangular matrix (sequence of sequence where every inner sequence is of the same length) this extension function produces the transposed matrix (rows and columns switched). + /// PRECONDITION: rectangular matrix -> The result for ragged sequences is not defined, and can change as an implementation detail. + /// + /// The transpose extension function only returns a transposed matrix for rectangular matrices. The sequence elements are yielded lazily however the outer sequence will be iterated greedily once to count its length. + /// A source matrix. + /// The type of the elements of the source matrix. + /// A partially lazy transposition of a matrix. + [Pure] + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration", Justification = "We need to know the length of the outer IEnumerable to Chunk correctly, we only iterate the outer sequence, which should be cheap")] + public static IAsyncEnumerable> Transpose(this IEnumerable> source) + => source.Any() + ? source.Interleave().Chunk(source.Count()) + : AsyncEnumerable.Empty>(); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/Traverse.cs b/Funcky/Extensions/AsyncEnumerableExtensions/Traverse.cs new file mode 100644 index 000000000..c4d0e8dd1 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/Traverse.cs @@ -0,0 +1,45 @@ +#if INTEGRATED_ASYNC +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + public static ValueTask>> TraverseAsync( + this IAsyncEnumerable source, + Func> selector, + CancellationToken cancellationToken = default) + where TLeft : notnull + where TRight : notnull + => source.Select(selector).SequenceAsync(cancellationToken); + + public static ValueTask>> TraverseAsync( + this IAsyncEnumerable source, + Func> selector, + CancellationToken cancellationToken = default) + where TItem : notnull + => source.Select(selector).SequenceAsync(cancellationToken); + + public static ValueTask>> TraverseAsync( + this IAsyncEnumerable source, + Func> selector, + CancellationToken cancellationToken = default) + where TValidResult : notnull + => source.Select(selector).SequenceAsync(cancellationToken); + + [Pure] + public static Reader> Traverse( + this IAsyncEnumerable source, + Func> selector) + where TEnvironment : notnull + where TResult : notnull + => source.Select(selector).Sequence(); + + [Pure] + public static Lazy> Traverse( + this IAsyncEnumerable source, + Func> selector) + => source.Select(selector).Sequence(); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WhereNotNull.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WhereNotNull.cs new file mode 100644 index 000000000..710c8623f --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WhereNotNull.cs @@ -0,0 +1,16 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + [Pure] + public static IAsyncEnumerable WhereNotNull(this IAsyncEnumerable source) + where TSource : class + => source.WhereSelect(Option.FromNullable); + + [Pure] + public static IAsyncEnumerable WhereNotNull(this IAsyncEnumerable source) + where TSource : struct + => source.WhereSelect(Option.FromNullable); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WhereSelect.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WhereSelect.cs new file mode 100644 index 000000000..3079e23b0 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WhereSelect.cs @@ -0,0 +1,61 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Filters out all the empty values from an >]]> and therefore returns an . + /// + [Pure] + public static IAsyncEnumerable WhereSelect(this IAsyncEnumerable> source) + where TSource : notnull + => source.WhereSelect(Identity); + + /// + /// Projects and filters an at the same time. + /// This is done by filtering out any empty values returned by the . + /// + [Pure] + public static IAsyncEnumerable WhereSelect(this IAsyncEnumerable source, Func> selector) + where TResult : notnull + => source.Select(selector).SelectMany(ToAsyncEnumerable); + + /// + /// Projects and filters an at the same time. + /// This is done by filtering out any empty values returned by the . + /// The index of each source element is used in the projected form of that element. + /// + [Pure] + public static IAsyncEnumerable WhereSelect(this IAsyncEnumerable source, Func> selector) + where TResult : notnull + => source.Select(selector).SelectMany(ToAsyncEnumerable); + + /// + [Pure] + public static IAsyncEnumerable WhereSelectAwait(this IAsyncEnumerable source, Func>> selector) + where TResult : notnull + => source.Select((TSource item, CancellationToken _) => selector(item)).SelectMany(ToAsyncEnumerable); + + /// + [Pure] + public static IAsyncEnumerable WhereSelectAwait(this IAsyncEnumerable source, Func>> selector) + where TResult : notnull + => source.Select((item, index, _) => selector(item, index)).SelectMany(ToAsyncEnumerable); + + /// + [Pure] + public static IAsyncEnumerable WhereSelectAwaitWithCancellation(this IAsyncEnumerable source, Func>> selector) + where TResult : notnull + => source.Select(selector).SelectMany(ToAsyncEnumerable); + + /// + [Pure] + public static IAsyncEnumerable WhereSelectAwaitWithCancellation(this IAsyncEnumerable source, Func>> selector) + where TResult : notnull + => source.Select(selector).SelectMany(ToAsyncEnumerable); + + private static IAsyncEnumerable ToAsyncEnumerable(Option option) + where TItem : notnull + => option.ToAsyncEnumerable(); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WithFirst.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WithFirst.cs new file mode 100644 index 000000000..e72b132fc --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WithFirst.cs @@ -0,0 +1,32 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns a sequence mapping each element into a type which has an IsFirst property which is true for the first element of the sequence. The returned struct is deconstructible. + /// + /// The source sequence. + /// Type of the elements in sequence. + /// Returns a sequence mapping each element into a type which has an IsFirst property which is true for the first element of the sequence. + [Pure] + public static async IAsyncEnumerable> WithFirst(this IAsyncEnumerable source) + { + #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using var enumerator = source.ConfigureAwait(false).GetAsyncEnumerator(); + #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + + if (!await enumerator.MoveNextAsync()) + { + yield break; + } + + yield return new ValueWithFirst(enumerator.Current, true); + + while (await enumerator.MoveNextAsync()) + { + yield return new ValueWithFirst(enumerator.Current, false); + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WithIndex.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WithIndex.cs new file mode 100644 index 000000000..d920cfeea --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WithIndex.cs @@ -0,0 +1,16 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns a sequence mapping each element together with an Index starting at 0. The returned struct is deconstructible. + /// + /// The source sequence. + /// Type of the elements in sequence. + /// Returns a sequence mapping each element together with an Index starting at 0. + [Pure] + public static IAsyncEnumerable> WithIndex(this IAsyncEnumerable source) + => source.Select((value, index) => new ValueWithIndex(value, index)); +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WithLast.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WithLast.cs new file mode 100644 index 000000000..f6e64b7be --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WithLast.cs @@ -0,0 +1,34 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Returns a sequence mapping each element into a type which has an IsLast property which is true for the last element of the sequence. The returned struct is deconstructible. + /// + /// The source sequence. + /// Type of the elements in sequence. + /// Returns a sequence mapping each element into a type which has an IsLast property which is true for the last element of the sequence. + [Pure] + public static async IAsyncEnumerable> WithLast(this IAsyncEnumerable source) + { + #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using var enumerator = source.ConfigureAwait(false).GetAsyncEnumerator(); + #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + + if (!await enumerator.MoveNextAsync()) + { + yield break; + } + + var current = enumerator.Current; + while (await enumerator.MoveNextAsync()) + { + yield return new ValueWithLast(current, false); + current = enumerator.Current; + } + + yield return new ValueWithLast(current, true); + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/WithPrevious.cs b/Funcky/Extensions/AsyncEnumerableExtensions/WithPrevious.cs new file mode 100644 index 000000000..83528fd8d --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/WithPrevious.cs @@ -0,0 +1,27 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// Returns a sequence mapping each element together with its predecessor. + /// Thrown when any value in is . + [Pure] + public static IAsyncEnumerable> WithPrevious(this IAsyncEnumerable source) + where TSource : notnull + => source.WithPreviousInternal(); + + private static async IAsyncEnumerable> WithPreviousInternal(this IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken = default) + where TSource : notnull + { + var previous = Option.None; + + await foreach (var value in source.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + yield return new ValueWithPrevious(value, previous); + previous = value; + } + } +} +#endif diff --git a/Funcky/Extensions/AsyncEnumerableExtensions/ZipLongest.cs b/Funcky/Extensions/AsyncEnumerableExtensions/ZipLongest.cs new file mode 100644 index 000000000..f24bf1482 --- /dev/null +++ b/Funcky/Extensions/AsyncEnumerableExtensions/ZipLongest.cs @@ -0,0 +1,59 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Extensions; + +public static partial class AsyncEnumerableExtensions +{ + /// + /// Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. + /// + /// The left sequence to merge. + /// Type of the elements in sequence. + /// The right sequence to merge. + /// Type of the elements in sequence. + /// A sequence that contains merged elements of two input sequences. + [Pure] + public static IAsyncEnumerable> ZipLongest(this IAsyncEnumerable left, IAsyncEnumerable right) + where TLeft : notnull + where TRight : notnull + => left.ZipLongest(right, Identity); + + /// + /// Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. + /// + /// The left sequence to merge. + /// Type of the elements in sequence. + /// The right sequence to merge. + /// Type of the elements in sequence. + /// The return type of the result selector function. + /// A function that specifies how to merge the elements from the two sequences. + /// A sequence that contains merged elements of two input sequences. + [Pure] + public static async IAsyncEnumerable ZipLongest(this IAsyncEnumerable left, IAsyncEnumerable right, Func, TResult> resultSelector) + where TLeft : notnull + where TRight : notnull + { + #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using var leftEnumerator = left.ConfigureAwait(false).GetAsyncEnumerator(); + await using var rightEnumerator = right.ConfigureAwait(false).GetAsyncEnumerator(); + #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + + while ((await MoveNextOrNone(leftEnumerator, rightEnumerator).ConfigureAwait(false)).TryGetValue(out var next)) + { + yield return resultSelector(next); + } + } + + private static async ValueTask>> MoveNextOrNone(ConfiguredCancelableAsyncEnumerable.Enumerator leftEnumerator, ConfiguredCancelableAsyncEnumerable.Enumerator rightEnumerator) + where TLeft : notnull + where TRight : notnull + => EitherOrBoth.FromOptions(await ReadNext(leftEnumerator).ConfigureAwait(false), await ReadNext(rightEnumerator).ConfigureAwait(false)); + + private static async ValueTask> ReadNext(ConfiguredCancelableAsyncEnumerable.Enumerator enumerator) + where TSource : notnull + => await enumerator.MoveNextAsync() + ? enumerator.Current + : Option.None; +} +#endif diff --git a/Funcky/Extensions/EnumerableExtensions/Grouping.cs b/Funcky/Extensions/EnumerableExtensions/Grouping.cs deleted file mode 100644 index 3342e113c..000000000 --- a/Funcky/Extensions/EnumerableExtensions/Grouping.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections; -using System.Collections.Immutable; - -namespace Funcky.Extensions; - -public static partial class EnumerableExtensions -{ - internal class Grouping(TKey key, IImmutableList elements) : IGrouping, IReadOnlyList, IList - { - public TKey Key => key; - - public int Count => elements.Count; - - public bool IsReadOnly => true; - - public TElement this[int index] - { - get => elements[index]; - set => throw new NotSupportedException(); - } - - public void Add(TElement item) - => throw new NotSupportedException(); - - public void Clear() - => throw new NotSupportedException(); - - public bool Contains(TElement element) - => elements.Contains(element); - - public void CopyTo(TElement[] array, int arrayIndex) - => throw new NotSupportedException(); - - public IEnumerator GetEnumerator() - => elements.GetEnumerator(); - - public int IndexOf(TElement element) - => elements.IndexOf(element); - - public void Insert(int index, TElement element) - => throw new NotSupportedException(); - - public bool Remove(TElement element) - => throw new NotSupportedException(); - - public void RemoveAt(int index) - => throw new NotSupportedException(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - } -} diff --git a/Funcky/Extensions/EnumerableExtensions/Scan.cs b/Funcky/Extensions/EnumerableExtensions/Scan.cs index 8d8b01e6b..e06f04e33 100644 --- a/Funcky/Extensions/EnumerableExtensions/Scan.cs +++ b/Funcky/Extensions/EnumerableExtensions/Scan.cs @@ -3,7 +3,7 @@ namespace Funcky.Extensions; public static partial class EnumerableExtensions { /// - /// Scan generates a sequence known as the the inclusive prefix sum. + /// Scan generates a sequence known as the inclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. @@ -23,7 +23,7 @@ public static IEnumerable InclusiveScan(this } /// - /// Scan generates a sequence known as the the exclusive prefix sum. + /// Scan generates a sequence known as the exclusive prefix sum. /// /// The type of the source elements. /// The seed and target type. diff --git a/Funcky/Extensions/EnumerableExtensions/Shuffle.cs b/Funcky/Extensions/EnumerableExtensions/Shuffle.cs index 5d6c9d0c9..f2abb4b8c 100644 --- a/Funcky/Extensions/EnumerableExtensions/Shuffle.cs +++ b/Funcky/Extensions/EnumerableExtensions/Shuffle.cs @@ -6,6 +6,7 @@ namespace Funcky.Extensions; public static partial class EnumerableExtensions { +#if !SHUFFLE_EXTENSION /// /// Returns the given sequence eagerly in random Order in O(n). /// @@ -14,6 +15,7 @@ public static partial class EnumerableExtensions [Pure] public static IReadOnlyList Shuffle(this IEnumerable source) => source.Shuffle(new Random()); +#endif /// /// Returns the given sequence eagerly in random Order in O(n). @@ -32,5 +34,5 @@ public static IReadOnlyList Shuffle(this IEnumerable => source .ToList() .ToRandomList(random); - #endif +#endif } diff --git a/Funcky/Funcky.csproj b/Funcky/Funcky.csproj index 56aad6bb0..5fb6f812e 100644 --- a/Funcky/Funcky.csproj +++ b/Funcky/Funcky.csproj @@ -1,13 +1,13 @@  - net9.0 - $(FunckyNewestTargetFramework);net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1 + net10.0 + $(FunckyNewestTargetFramework);net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1 preview enable Funcky Funcky is a functional C# library Functional Monad Linq - 3.5.1 + 3.6.0 true snupkg README.md @@ -17,6 +17,7 @@ true All true + $(NoWarn);RS0026 true diff --git a/Funcky/Functional/RetryAsync.cs b/Funcky/Functional/RetryAsync.cs new file mode 100644 index 000000000..643b5b700 --- /dev/null +++ b/Funcky/Functional/RetryAsync.cs @@ -0,0 +1,48 @@ +#if INTEGRATED_ASYNC +using Funcky.RetryPolicies; + +namespace Funcky; + +public static partial class AsyncFunctional +{ + /// + /// Calls the given over and over until it returns a value. + /// + public static async ValueTask RetryAsync(Func>> producer, CancellationToken cancellationToken = default) + where TResult : notnull + { + cancellationToken.ThrowIfCancellationRequested(); + return await (await producer().ConfigureAwait(false)).Match( + none: (Func>)(async () => await RetryAsync(producer, cancellationToken).ConfigureAwait(false)), + some: result => new ValueTask(result)).ConfigureAwait(false); + } + + public static async ValueTask> RetryAsync(Func>> producer, IRetryPolicy retryPolicy, CancellationToken cancellationToken = default) + where TResult : notnull + { + cancellationToken.ThrowIfCancellationRequested(); + return await AsyncSequence + .Return(await producer().ConfigureAwait(false)) + .Concat(TailRetriesAsync(producer, retryPolicy, cancellationToken)) + .WhereSelect(Identity) + .FirstOrNoneAsync(cancellationToken) + .ConfigureAwait(false); + } + + private static IAsyncEnumerable> TailRetriesAsync(Func>> producer, IRetryPolicy retryPolicy, CancellationToken cancellationToken) + where TResult : notnull + => Retries(retryPolicy) + .Select((int item, CancellationToken _) => ProduceDelayedAsync(producer, retryPolicy, cancellationToken)(item)); + + private static IAsyncEnumerable Retries(IRetryPolicy retryPolicy) + => AsyncEnumerable.Range(0, retryPolicy.MaxRetries); + + private static Func>> ProduceDelayedAsync(Func>> producer, IRetryPolicy retryPolicy, CancellationToken cancellationToken) + where TResult : notnull + => async retryCount => + { + await Task.Delay(retryPolicy.Delay(retryCount), cancellationToken).ConfigureAwait(false); + return await producer().ConfigureAwait(false); + }; +} +#endif diff --git a/Funcky/Functional/RetryWithExceptionAsync.cs b/Funcky/Functional/RetryWithExceptionAsync.cs new file mode 100644 index 000000000..8ed281b0f --- /dev/null +++ b/Funcky/Functional/RetryWithExceptionAsync.cs @@ -0,0 +1,64 @@ +#if INTEGRATED_ASYNC +using Funcky.RetryPolicies; +using static Funcky.ValueTaskFactory; + +namespace Funcky; + +public static partial class AsyncFunctional +{ + /// Retries a producer as long as an exception matching the predicate is thrown. + /// When all retries are exhausted, the exception is propagated to the caller. + public static ValueTask RetryAsync( + Func producer, + Func shouldRetry, + IRetryPolicy retryPolicy, + CancellationToken cancellationToken = default) + => RetryAwaitAsync(() => ValueTaskFromResult(producer()), shouldRetry, retryPolicy, cancellationToken); + + /// + public static async ValueTask RetryAsync( + Action action, + Func shouldRetry, + IRetryPolicy retryPolicy, + CancellationToken cancellationToken = default) + => await RetryAsync(ActionToUnit(action), shouldRetry, retryPolicy, cancellationToken).ConfigureAwait(false); + + /// + public static async ValueTask RetryAwaitAsync( + Func> producer, + Func shouldRetry, + IRetryPolicy retryPolicy, + CancellationToken cancellationToken = default) + { + var retryCount = 0; + while (true) + { + try + { + return await producer().ConfigureAwait(false); + } + catch (Exception exception) when (shouldRetry(exception) && retryCount < retryPolicy.MaxRetries) + { + retryCount++; + await Task.Delay(retryPolicy.Delay(retryCount), cancellationToken).ConfigureAwait(false); + } + } + } + + /// + public static async ValueTask RetryAwaitAsync( + Func action, + Func shouldRetry, + IRetryPolicy retryPolicy, + CancellationToken cancellationToken = default) + { + await RetryAwaitAsync(Producer, shouldRetry, retryPolicy, cancellationToken).ConfigureAwait(false); + + async ValueTask Producer() + { + await action().ConfigureAwait(false); + return Unit.Value; + } + } +} +#endif diff --git a/Funcky/Grouping.cs b/Funcky/Grouping.cs new file mode 100644 index 000000000..d9eb0974d --- /dev/null +++ b/Funcky/Grouping.cs @@ -0,0 +1,49 @@ +using System.Collections; +using System.Collections.Immutable; + +namespace Funcky; + +internal class Grouping(TKey key, IImmutableList elements) : IGrouping, IReadOnlyList, IList +{ + public TKey Key => key; + + public int Count => elements.Count; + + public bool IsReadOnly => true; + + public TElement this[int index] + { + get => elements[index]; + set => throw new NotSupportedException(); + } + + public void Add(TElement item) + => throw new NotSupportedException(); + + public void Clear() + => throw new NotSupportedException(); + + public bool Contains(TElement element) + => elements.Contains(element); + + public void CopyTo(TElement[] array, int arrayIndex) + => throw new NotSupportedException(); + + public IEnumerator GetEnumerator() + => elements.GetEnumerator(); + + public int IndexOf(TElement element) + => elements.IndexOf(element); + + public void Insert(int index, TElement element) + => throw new NotSupportedException(); + + public bool Remove(TElement element) + => throw new NotSupportedException(); + + public void RemoveAt(int index) + => throw new NotSupportedException(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); +} diff --git a/Funcky/IAsyncBuffer.cs b/Funcky/IAsyncBuffer.cs new file mode 100644 index 000000000..055376075 --- /dev/null +++ b/Funcky/IAsyncBuffer.cs @@ -0,0 +1,9 @@ +#if INTEGRATED_ASYNC +namespace Funcky; + +/// +/// Represents a buffer of an underlying resource and is accordingly. +/// +/// Element type. +public interface IAsyncBuffer : IAsyncEnumerable, IAsyncDisposable; +#endif diff --git a/Funcky/Monads/Either/EitherAsyncExtensions.Traversable.cs b/Funcky/Monads/Either/EitherAsyncExtensions.Traversable.cs new file mode 100644 index 000000000..ac3e2fe61 --- /dev/null +++ b/Funcky/Monads/Either/EitherAsyncExtensions.Traversable.cs @@ -0,0 +1,62 @@ +#if INTEGRATED_ASYNC +using static Funcky.ValueTaskFactory; + +namespace Funcky.Monads; + +public static class EitherAsyncExtensions +{ + [Pure] + public static IAsyncEnumerable> Traverse( + this Either either, + Func> selector) + where TLeft : notnull + where TRight : notnull + where T : notnull + => either.Select(selector).Sequence(); + + [Pure] + public static IAsyncEnumerable> Sequence( + this Either> either) + where TLeft : notnull + where TRight : notnull + => either.Match( + left: static left => AsyncSequence.Return(Either.Left(left)), + right: static right => right.Select(Either.Return)); + + [Pure] + public static Task> Traverse( + this Either either, + Func> selector) + where TLeft : notnull + where TRight : notnull + where T : notnull + => either.Select(selector).Sequence(); + + [Pure] + public static Task> Sequence( + this Either> either) + where TLeft : notnull + where TRight : notnull + => either.Match( + left: static left => Task.FromResult(Either.Left(left)), + right: static async right => Either.Return(await right.ConfigureAwait(false))); + + [Pure] + public static ValueTask> Traverse( + this Either either, + Func> selector) + where TLeft : notnull + where TRight : notnull + where T : notnull + => either.Select(selector).Sequence(); + + [Pure] + public static ValueTask> Sequence( + this Either> either) + where TLeft : notnull + where TRight : notnull + => either.Match>>( + left: static left => ValueTaskFromResult(Either.Left(left)), + right: static async right => Either.Return(await right.ConfigureAwait(false))); +} +#endif diff --git a/Funcky/Monads/Option/ConfiguredOptionTaskAwaitable.cs b/Funcky/Monads/Option/ConfiguredOptionTaskAwaitable.cs new file mode 100644 index 000000000..efcca6b99 --- /dev/null +++ b/Funcky/Monads/Option/ConfiguredOptionTaskAwaitable.cs @@ -0,0 +1,51 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Monads; + +public readonly struct ConfiguredOptionTaskAwaitable + where TItem : notnull +{ + private readonly Option> _awaitable; + + internal ConfiguredOptionTaskAwaitable(Option> awaitable) => _awaitable = awaitable; + + public ConfiguredOptionTaskAwaiter GetAwaiter() => new(_awaitable.Select(awaitable => awaitable.GetAwaiter())); + + public readonly struct ConfiguredOptionTaskAwaiter : INotifyCompletion + { + private readonly Option.ConfiguredTaskAwaiter> _awaiter; + + internal ConfiguredOptionTaskAwaiter(Option.ConfiguredTaskAwaiter> awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter.Select(awaiter => awaiter.IsCompleted).GetOrElse(true); + + public void OnCompleted(Action continuation) => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public Option GetResult() => _awaiter.Select(awaiter => awaiter.GetResult()); + } +} + +public readonly struct ConfiguredOptionTaskAwaitable +{ + private readonly Option _awaitable; + + internal ConfiguredOptionTaskAwaitable(Option awaitable) => _awaitable = awaitable; + + public ConfiguredOptionTaskAwaiter GetAwaiter() => new(_awaitable.Select(awaitable => awaitable.GetAwaiter())); + + public readonly struct ConfiguredOptionTaskAwaiter : INotifyCompletion + { + private readonly Option _awaiter; + + internal ConfiguredOptionTaskAwaiter(Option awaiter) => + _awaiter = awaiter; + + public bool IsCompleted => _awaiter.Select(awaiter => awaiter.IsCompleted).GetOrElse(true); + + public void OnCompleted(Action continuation) => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public void GetResult() => _awaiter.AndThen(awaiter => awaiter.GetResult()); + } +} +#endif diff --git a/Funcky/Monads/Option/ConfiguredOptionValueTaskAwaitable.cs b/Funcky/Monads/Option/ConfiguredOptionValueTaskAwaitable.cs new file mode 100644 index 000000000..b18247ec5 --- /dev/null +++ b/Funcky/Monads/Option/ConfiguredOptionValueTaskAwaitable.cs @@ -0,0 +1,52 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky.Monads; + +public readonly struct ConfiguredOptionValueTaskAwaitable + where TItem : notnull +{ + private readonly Option> _awaitable; + + internal ConfiguredOptionValueTaskAwaitable(Option> awaitable) => _awaitable = awaitable; + + public ConfiguredOptionValueTaskAwaiter GetAwaiter() => new(_awaitable.Select(awaitable => awaitable.GetAwaiter())); + + public readonly struct ConfiguredOptionValueTaskAwaiter : INotifyCompletion + { + private readonly Option.ConfiguredValueTaskAwaiter> _awaiter; + + internal ConfiguredOptionValueTaskAwaiter(Option.ConfiguredValueTaskAwaiter> awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter.Select(awaiter => awaiter.IsCompleted).GetOrElse(true); + + public void OnCompleted(Action continuation) => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public Option GetResult() => _awaiter.Select(task => task.GetResult()); + } +} + +public readonly struct ConfiguredOptionValueTaskAwaitable +{ + private readonly Option _awaitable; + + internal ConfiguredOptionValueTaskAwaitable(Option awaitable) => + _awaitable = awaitable; + + public ConfiguredOptionValueTaskAwaiter GetAwaiter() => new(_awaitable.Select(awaitable => awaitable.GetAwaiter())); + + public readonly struct ConfiguredOptionValueTaskAwaiter : INotifyCompletion + { + private readonly Option _awaiter; + + internal ConfiguredOptionValueTaskAwaiter( + Option awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter.Select(awaiter => awaiter.IsCompleted).GetOrElse(true); + + public void OnCompleted(Action continuation) => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public void GetResult() => _awaiter.AndThen(awaiter => awaiter.GetResult()); + } +} +#endif diff --git a/Funcky/Monads/Option/OptionAsyncExtensions.GetAwaiter.cs b/Funcky/Monads/Option/OptionAsyncExtensions.GetAwaiter.cs new file mode 100644 index 000000000..83b6feb0e --- /dev/null +++ b/Funcky/Monads/Option/OptionAsyncExtensions.GetAwaiter.cs @@ -0,0 +1,60 @@ +#if INTEGRATED_ASYNC +using System.ComponentModel; + +namespace Funcky.Monads; + +public static partial class OptionAsyncExtensions +{ + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static OptionTaskAwaiter GetAwaiter(this Option option) + => new(option.GetOrElse(Task.CompletedTask).GetAwaiter()); + + /// Configures an awaiter used to await this . + /// The option to await. + /// + /// to attempt to marshal the continuation back to the original context captured; otherwise, . + /// An object used to await this task. + public static ConfiguredOptionTaskAwaitable ConfigureAwait(this Option option, bool continueOnCapturedContext) + => new(option.Select(task => task.ConfigureAwait(continueOnCapturedContext))); + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static OptionTaskAwaiter GetAwaiter(this Option> option) + where TItem : notnull + => new(option.Select(task => task.GetAwaiter())); + + /// Configures an awaiter used to await this . + /// The option to await. + /// + /// to attempt to marshal the continuation back to the original context captured; otherwise, . + /// An object used to await this task. + public static ConfiguredOptionTaskAwaitable ConfigureAwait(this Option> option, bool continueOnCapturedContext) + where TItem : notnull + => new(option.Select(task => task.ConfigureAwait(continueOnCapturedContext))); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "False positive.")] + public static OptionValueTaskAwaiter GetAwaiter(this Option option) + => new(option.GetOrElse(default(ValueTask)).GetAwaiter()); + + /// Configures an awaiter used to await this . + /// The option to await. + /// + /// to attempt to marshal the continuation back to the original context captured; otherwise, . + /// An object used to await this task. + public static ConfiguredOptionValueTaskAwaitable ConfigureAwait(this Option option, bool continueOnCapturedContext) + => new(option.Select(task => task.ConfigureAwait(continueOnCapturedContext))); + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static OptionValueTaskAwaiter GetAwaiter(this Option> option) + where TItem : notnull + => new(option.Select(task => task.GetAwaiter())); + + /// Configures an awaiter used to await this . + /// The option to await. + /// + /// to attempt to marshal the continuation back to the original context captured; otherwise, . + /// An object used to await this ValueTask. + public static ConfiguredOptionValueTaskAwaitable ConfigureAwait(this Option> option, bool continueOnCapturedContext) + where TItem : notnull + => new(option.Select(task => task.ConfigureAwait(continueOnCapturedContext))); +} +#endif diff --git a/Funcky/Monads/Option/OptionAsyncExtensions.Traversable.cs b/Funcky/Monads/Option/OptionAsyncExtensions.Traversable.cs new file mode 100644 index 000000000..b51e09058 --- /dev/null +++ b/Funcky/Monads/Option/OptionAsyncExtensions.Traversable.cs @@ -0,0 +1,56 @@ +#if INTEGRATED_ASYNC +using static Funcky.ValueTaskFactory; + +namespace Funcky.Monads; + +public static partial class OptionAsyncExtensions +{ + [Pure] + public static IAsyncEnumerable> Traverse( + this Option option, + Func> selector) + where TItem : notnull + where T : notnull + => option.Select(selector).Sequence(); + + [Pure] + public static IAsyncEnumerable> Sequence( + this Option> option) + where TItem : notnull + => option.Match( + none: AsyncSequence.Return(Option.None), + some: static item => item.Select(Option.Return)); + + [Pure] + public static Task> Traverse( + this Option option, + Func> selector) + where TItem : notnull + where T : notnull + => option.Select(selector).Sequence(); + + [Pure] + public static Task> Sequence( + this Option> option) + where TItem : notnull + => option.Match( + none: static () => Task.FromResult(Option.None), + some: static async item => Option.Return(await item.ConfigureAwait(false))); + + [Pure] + public static ValueTask> Traverse( + this Option option, + Func> selector) + where TItem : notnull + where T : notnull + => option.Select(selector).Sequence(); + + [Pure] + public static ValueTask> Sequence( + this Option> option) + where TItem : notnull + => option.Match>>( + none: static () => ValueTaskFromResult(Option.None), + some: static async item => Option.Return(await item.ConfigureAwait(false))); +} +#endif diff --git a/Funcky/Monads/Option/OptionExtensions.ToAsyncEnumerable.cs b/Funcky/Monads/Option/OptionExtensions.ToAsyncEnumerable.cs new file mode 100644 index 000000000..5ee6236ea --- /dev/null +++ b/Funcky/Monads/Option/OptionExtensions.ToAsyncEnumerable.cs @@ -0,0 +1,16 @@ +#if INTEGRATED_ASYNC +namespace Funcky.Monads; + +public static partial class OptionAsyncExtensions +{ + /// + /// Returns an that yields exactly one value when the option + /// has an item and nothing when the option is empty. + /// + public static IAsyncEnumerable ToAsyncEnumerable(this Option option) + where TItem : notnull + => option.Match( + none: AsyncEnumerable.Empty, + some: AsyncSequence.Return); +} +#endif diff --git a/Funcky/Monads/Option/OptionTaskAwaiter.cs b/Funcky/Monads/Option/OptionTaskAwaiter.cs new file mode 100644 index 000000000..0ec58baeb --- /dev/null +++ b/Funcky/Monads/Option/OptionTaskAwaiter.cs @@ -0,0 +1,39 @@ +#if INTEGRATED_ASYNC +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Funcky.Monads; + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public readonly struct OptionTaskAwaiter : INotifyCompletion + where TItem : notnull +{ + private readonly Option> _awaiter; + + internal OptionTaskAwaiter(Option> awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter + .Select(awaiter => awaiter.IsCompleted) + .GetOrElse(true); + + public void OnCompleted(Action continuation) + => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public Option GetResult() + => _awaiter.Select(awaiter => awaiter.GetResult()); +} + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public readonly struct OptionTaskAwaiter : INotifyCompletion +{ + private readonly TaskAwaiter _awaiter; + + internal OptionTaskAwaiter(TaskAwaiter awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter.IsCompleted; + + public void OnCompleted(Action continuation) => _awaiter.OnCompleted(continuation); + + public void GetResult() => _awaiter.GetResult(); +} +#endif diff --git a/Funcky/Monads/Option/OptionValueTaskAwaiter.cs b/Funcky/Monads/Option/OptionValueTaskAwaiter.cs new file mode 100644 index 000000000..4730bc4b5 --- /dev/null +++ b/Funcky/Monads/Option/OptionValueTaskAwaiter.cs @@ -0,0 +1,39 @@ +#if INTEGRATED_ASYNC +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Funcky.Monads; + +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct OptionValueTaskAwaiter : INotifyCompletion + where TItem : notnull +{ + private readonly Option> _awaiter; + + internal OptionValueTaskAwaiter(Option> awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter + .Select(awaiter => awaiter.IsCompleted) + .GetOrElse(true); + + public void OnCompleted(Action continuation) + => _awaiter.AndThen(awaiter => awaiter.OnCompleted(continuation)); + + public Option GetResult() + => _awaiter.Select(awaiter => awaiter.GetResult()); +} + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public readonly struct OptionValueTaskAwaiter : INotifyCompletion +{ + private readonly ValueTaskAwaiter _awaiter; + + internal OptionValueTaskAwaiter(ValueTaskAwaiter awaiter) => _awaiter = awaiter; + + public bool IsCompleted => _awaiter.IsCompleted; + + public void OnCompleted(Action continuation) => _awaiter.OnCompleted(continuation); + + public void GetResult() => _awaiter.GetResult(); +} +#endif diff --git a/Funcky/Monads/Result/ResultAsyncExtensions.Traversable.cs b/Funcky/Monads/Result/ResultAsyncExtensions.Traversable.cs new file mode 100644 index 000000000..ce7795111 --- /dev/null +++ b/Funcky/Monads/Result/ResultAsyncExtensions.Traversable.cs @@ -0,0 +1,56 @@ +#if INTEGRATED_ASYNC +using static Funcky.ValueTaskFactory; + +namespace Funcky.Monads; + +public static class ResultAsyncExtensions +{ + [Pure] + public static IAsyncEnumerable> Traverse( + this Result result, + Func> selector) + where TValidResult : notnull + where T : notnull + => result.Select(selector).Sequence(); + + [Pure] + public static IAsyncEnumerable> Sequence( + this Result> result) + where TValidResult : notnull + => result.Match( + error: static error => AsyncSequence.Return(Result.Error(error)), + ok: static ok => ok.Select(Result.Return)); + + [Pure] + public static Task> Traverse( + this Result result, + Func> selector) + where TValidResult : notnull + where T : notnull + => result.Select(selector).Sequence(); + + [Pure] + public static Task> Sequence( + this Result> result) + where TValidResult : notnull + => result.Match( + error: static error => Task.FromResult(Result.Error(error)), + ok: static async ok => Result.Return(await ok.ConfigureAwait(false))); + + [Pure] + public static ValueTask> Traverse( + this Result result, + Func> selector) + where TValidResult : notnull + where T : notnull + => result.Select(selector).Sequence(); + + [Pure] + public static ValueTask> Sequence( + this Result> result) + where TValidResult : notnull + => result.Match>>( + error: static error => ValueTaskFromResult(Result.Error(error)), + ok: static async ok => Result.Return(await ok.ConfigureAwait(false))); +} +#endif diff --git a/Funcky/PublicAPI.Shipped.txt b/Funcky/PublicAPI.Shipped.txt index c7b1e8bc0..8e6c01981 100644 --- a/Funcky/PublicAPI.Shipped.txt +++ b/Funcky/PublicAPI.Shipped.txt @@ -1,4 +1,6 @@ #nullable enable +Funcky.AsyncFunctional +Funcky.AsyncSequence Funcky.Discard Funcky.DownCast Funcky.EitherOrBoth @@ -8,6 +10,7 @@ Funcky.EitherOrBoth.Equals(Funcky.EitherOrBoth oth Funcky.EitherOrBoth.Match(System.Func! left, System.Func! right, System.Func! both) -> TMatchResult Funcky.EitherOrBoth.Switch(System.Action! left, System.Action! right, System.Action! both) -> void Funcky.Extensions.ActionExtensions +Funcky.Extensions.AsyncEnumerableExtensions Funcky.Extensions.DictionaryExtensions Funcky.Extensions.EitherPartitions Funcky.Extensions.EitherPartitions @@ -22,7 +25,9 @@ Funcky.Extensions.FuncExtensions Funcky.Extensions.HttpHeadersExtensions Funcky.Extensions.HttpHeadersNonValidatedExtensions Funcky.Extensions.ImmutableListExtensions +Funcky.Extensions.JsonSerializerOptionsExtensions Funcky.Extensions.ListExtensions +Funcky.Extensions.OrderedDictionaryExtensions Funcky.Extensions.ParseExtensions Funcky.Extensions.Partitions Funcky.Extensions.Partitions @@ -69,7 +74,40 @@ Funcky.Extensions.ValueWithPrevious.Value.get -> TValue Funcky.Extensions.ValueWithPrevious.ValueWithPrevious() -> void Funcky.Extensions.ValueWithPrevious.ValueWithPrevious(TValue value, Funcky.Monads.Option previous) -> void Funcky.Functional +Funcky.IAsyncBuffer Funcky.IBuffer +Funcky.Monads.ConfiguredOptionTaskAwaitable +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaitable() -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.ConfiguredOptionTaskAwaiter() -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.GetResult() -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.GetAwaiter() -> Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter +Funcky.Monads.ConfiguredOptionTaskAwaitable +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaitable() -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.ConfiguredOptionTaskAwaiter() -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.GetResult() -> Funcky.Monads.Option +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.ConfiguredOptionTaskAwaitable.GetAwaiter() -> Funcky.Monads.ConfiguredOptionTaskAwaitable.ConfiguredOptionTaskAwaiter +Funcky.Monads.ConfiguredOptionValueTaskAwaitable +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaitable() -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.ConfiguredOptionValueTaskAwaiter() -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.GetResult() -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.GetAwaiter() -> Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter +Funcky.Monads.ConfiguredOptionValueTaskAwaitable +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaitable() -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.ConfiguredOptionValueTaskAwaiter() -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.GetResult() -> Funcky.Monads.Option +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.ConfiguredOptionValueTaskAwaitable.GetAwaiter() -> Funcky.Monads.ConfiguredOptionValueTaskAwaitable.ConfiguredOptionValueTaskAwaiter Funcky.Monads.Either Funcky.Monads.Either.Either() -> void Funcky.Monads.Either.Equals(Funcky.Monads.Either other) -> bool @@ -87,7 +125,10 @@ Funcky.Monads.Either.SelectMany(System.Func.SelectMany(System.Func>! selector) -> Funcky.Monads.Either Funcky.Monads.Either.Switch(System.Action! left, System.Action! right) -> void Funcky.Monads.Either +Funcky.Monads.EitherAsyncExtensions Funcky.Monads.EitherExtensions +Funcky.Monads.IEither +Funcky.Monads.IOption Funcky.Monads.Lazy Funcky.Monads.LazyExtensions Funcky.Monads.Option @@ -116,6 +157,7 @@ Funcky.Monads.Option.this[int index].get -> TItem Funcky.Monads.Option.ToEnumerable() -> System.Collections.Generic.IEnumerable! Funcky.Monads.Option.TryGetValue(out TItem? item) -> bool Funcky.Monads.Option.Where(System.Func! predicate) -> Funcky.Monads.Option +Funcky.Monads.OptionAsyncExtensions Funcky.Monads.OptionComparer Funcky.Monads.OptionComparer Funcky.Monads.OptionEqualityComparer @@ -123,6 +165,26 @@ Funcky.Monads.OptionEqualityComparer Funcky.Monads.OptionExtensions Funcky.Monads.OptionJsonConverter Funcky.Monads.OptionJsonConverter.OptionJsonConverter() -> void +Funcky.Monads.OptionTaskAwaiter +Funcky.Monads.OptionTaskAwaiter.GetResult() -> void +Funcky.Monads.OptionTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.OptionTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.OptionTaskAwaiter.OptionTaskAwaiter() -> void +Funcky.Monads.OptionTaskAwaiter +Funcky.Monads.OptionTaskAwaiter.GetResult() -> Funcky.Monads.Option +Funcky.Monads.OptionTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.OptionTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.OptionTaskAwaiter.OptionTaskAwaiter() -> void +Funcky.Monads.OptionValueTaskAwaiter +Funcky.Monads.OptionValueTaskAwaiter.GetResult() -> void +Funcky.Monads.OptionValueTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.OptionValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.OptionValueTaskAwaiter.OptionValueTaskAwaiter() -> void +Funcky.Monads.OptionValueTaskAwaiter +Funcky.Monads.OptionValueTaskAwaiter.GetResult() -> Funcky.Monads.Option +Funcky.Monads.OptionValueTaskAwaiter.IsCompleted.get -> bool +Funcky.Monads.OptionValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void +Funcky.Monads.OptionValueTaskAwaiter.OptionValueTaskAwaiter() -> void Funcky.Monads.Reader Funcky.Monads.Reader Funcky.Monads.ReaderExtensions @@ -142,6 +204,7 @@ Funcky.Monads.Result.Select(System.Func.SelectMany(System.Func>! selector) -> Funcky.Monads.Result Funcky.Monads.Result.SelectMany(System.Func>! selector, System.Func! resultSelector) -> Funcky.Monads.Result Funcky.Monads.Result.Switch(System.Action! ok, System.Action! error) -> void +Funcky.Monads.ResultAsyncExtensions Funcky.Monads.ResultExtensions Funcky.RequireClass Funcky.RequireClass.RequireClass() -> void @@ -189,15 +252,36 @@ override Funcky.Monads.Result.GetHashCode() -> int override Funcky.Monads.Result.ToString() -> string! override Funcky.Unit.Equals(object? obj) -> bool override Funcky.Unit.GetHashCode() -> int +static Funcky.AsyncFunctional.RetryAsync(System.Action! action, System.Func! shouldRetry, Funcky.RetryPolicies.IRetryPolicy! retryPolicy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.AsyncFunctional.RetryAsync(System.Func>>! producer, Funcky.RetryPolicies.IRetryPolicy! retryPolicy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.AsyncFunctional.RetryAsync(System.Func>>! producer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.AsyncFunctional.RetryAsync(System.Func! producer, System.Func! shouldRetry, Funcky.RetryPolicies.IRetryPolicy! retryPolicy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.AsyncFunctional.RetryAwaitAsync(System.Func! action, System.Func! shouldRetry, Funcky.RetryPolicies.IRetryPolicy! retryPolicy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.AsyncFunctional.RetryAwaitAsync(System.Func>! producer, System.Func! shouldRetry, Funcky.RetryPolicies.IRetryPolicy! retryPolicy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.AsyncSequence.Concat(System.Collections.Generic.IAsyncEnumerable!>! sources) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Concat(System.Collections.Generic.IAsyncEnumerable!>! sources) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Concat(System.Collections.Generic.IEnumerable!>! sources) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Concat(params System.Collections.Generic.IAsyncEnumerable![]! sources) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Cycle(TResult element) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.CycleRange(System.Collections.Generic.IAsyncEnumerable! sequence) -> Funcky.IAsyncBuffer! +static Funcky.AsyncSequence.FromNullable(TResult? element) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.FromNullable(TResult? element) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.RepeatRange(System.Collections.Generic.IAsyncEnumerable! source, int count) -> Funcky.IAsyncBuffer! +static Funcky.AsyncSequence.Return(TResult element) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Return(params TResult[]! elements) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Successors(Funcky.Monads.Option first, System.Func>>! successor) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Successors(Funcky.Monads.Option first, System.Func>! successor) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Successors(TResult first, System.Func>>! successor) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.AsyncSequence.Successors(TResult first, System.Func>! successor) -> System.Collections.Generic.IAsyncEnumerable! static Funcky.DownCast.From(Funcky.Monads.Option option) -> Funcky.Monads.Option static Funcky.DownCast.From(Funcky.Monads.Result result) -> Funcky.Monads.Result static Funcky.DownCast.From(Funcky.Monads.Either either, System.Func! failedCast) -> Funcky.Monads.Either static Funcky.EitherOrBoth.FromOptions(Funcky.Monads.Option left, Funcky.Monads.Option right) -> Funcky.Monads.Option> static Funcky.EitherOrBoth.Both(TLeft left, TRight right) -> Funcky.EitherOrBoth static Funcky.EitherOrBoth.Left(TLeft left) -> Funcky.EitherOrBoth +static Funcky.EitherOrBoth.Right(TRight right) -> Funcky.EitherOrBoth static Funcky.EitherOrBoth.operator !=(Funcky.EitherOrBoth left, Funcky.EitherOrBoth right) -> bool static Funcky.EitherOrBoth.operator ==(Funcky.EitherOrBoth left, Funcky.EitherOrBoth right) -> bool -static Funcky.EitherOrBoth.Right(TRight right) -> Funcky.EitherOrBoth static Funcky.Extensions.ActionExtensions.Compose(this System.Action! f, System.Func! g) -> System.Action! static Funcky.Extensions.ActionExtensions.Compose(this System.Action! f, System.Func! g) -> System.Action! static Funcky.Extensions.ActionExtensions.Curry(this System.Action! action) -> System.Func!>!>!>!>!>!>!>! @@ -221,8 +305,180 @@ static Funcky.Extensions.ActionExtensions.Uncurry(this Syste static Funcky.Extensions.ActionExtensions.Uncurry(this System.Func!>!>!>! action) -> System.Action! static Funcky.Extensions.ActionExtensions.Uncurry(this System.Func!>!>! action) -> System.Action! static Funcky.Extensions.ActionExtensions.Uncurry(this System.Func!>! action) -> System.Action! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! elementSelector, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! elementSelector, System.Func!, TResult>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! elementSelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! elementSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func!, TResult>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Func!, System.Threading.Tasks.ValueTask>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Func!, System.Threading.Tasks.ValueTask>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func!, System.Threading.Tasks.ValueTask>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func!, System.Threading.Tasks.ValueTask>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Func!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Func!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func>! elementSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Func!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AdjacentGroupByAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! keySelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.AnyOrElse(this System.Collections.Generic.IAsyncEnumerable! source, System.Collections.Generic.IAsyncEnumerable! fallback) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AnyOrElse(this System.Collections.Generic.IAsyncEnumerable! source, System.Func!>! fallback) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.AverageOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.ConcatToStringAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.ElementAtOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Index index, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.ElementAtOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, int index, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.ExclusiveScan(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.ExclusiveScanAwait(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func>! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.ExclusiveScanAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func>! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.FirstOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.FirstOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.FirstOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.FirstOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.InclusiveScan(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.InclusiveScanAwait(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func>! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.InclusiveScanAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, TAccumulate seed, System.Func>! accumulator) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Inspect(this System.Collections.Generic.IAsyncEnumerable! source, System.Action! inspector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.InspectAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! inspector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.InspectAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! inspector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.InspectEmpty(this System.Collections.Generic.IAsyncEnumerable! source, System.Action! inspector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Interleave(this System.Collections.Generic.IAsyncEnumerable! source, params System.Collections.Generic.IAsyncEnumerable![]! otherSources) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Interleave(this System.Collections.Generic.IEnumerable!>! source) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Intersperse(this System.Collections.Generic.IAsyncEnumerable! source, TSource element) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.JoinToStringAsync(this System.Collections.Generic.IAsyncEnumerable! source, char separator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.JoinToStringAsync(this System.Collections.Generic.IAsyncEnumerable! source, string! separator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.LastOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.LastOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.LastOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.LastOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaterializeAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! materializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!> +static Funcky.Extensions.AsyncEnumerableExtensions.MaterializeAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MaxOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.Memoize(this System.Collections.Generic.IAsyncEnumerable! source) -> Funcky.IAsyncBuffer! +static Funcky.Extensions.AsyncEnumerableExtensions.Merge(this System.Collections.Generic.IAsyncEnumerable! source1, System.Collections.Generic.IAsyncEnumerable! source2, Funcky.Monads.Option!> comparer = default(Funcky.Monads.Option!>)) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Merge(this System.Collections.Generic.IAsyncEnumerable! source1, System.Collections.Generic.IAsyncEnumerable! source2, System.Collections.Generic.IAsyncEnumerable! source3, Funcky.Monads.Option!> comparer = default(Funcky.Monads.Option!>)) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Merge(this System.Collections.Generic.IAsyncEnumerable! source1, System.Collections.Generic.IAsyncEnumerable! source2, System.Collections.Generic.IAsyncEnumerable! source3, System.Collections.Generic.IAsyncEnumerable! source4, Funcky.Monads.Option!> comparer = default(Funcky.Monads.Option!>)) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Merge(this System.Collections.Generic.IEnumerable!>! sources, Funcky.Monads.Option!> comparer = default(Funcky.Monads.Option!>)) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.MinOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.NoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.NoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.NoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.NoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.Pairwise(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Pairwise(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable<(TSource Front, TSource Back)>! +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Func!, System.Collections.Generic.IReadOnlyList!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Func!, System.Collections.Generic.IReadOnlyList!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IReadOnlyList!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Func!, System.Collections.Generic.IReadOnlyList!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Func!, System.Collections.Generic.IReadOnlyList!, System.Threading.Tasks.ValueTask>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Func!, System.Collections.Generic.IReadOnlyList!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +static Funcky.Extensions.AsyncEnumerableExtensions.PartitionAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.PowerSet(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Sequence(this System.Collections.Generic.IAsyncEnumerable!>! source) -> Funcky.Monads.Reader!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Sequence(this System.Collections.Generic.IAsyncEnumerable!>! source) -> System.Lazy!>! +static Funcky.Extensions.AsyncEnumerableExtensions.SequenceAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.SequenceAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.SequenceAsync(this System.Collections.Generic.IAsyncEnumerable>! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.ShuffleAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Random! random, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!> +static Funcky.Extensions.AsyncEnumerableExtensions.ShuffleAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!> +static Funcky.Extensions.AsyncEnumerableExtensions.SingleOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.SingleOrNoneAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.SingleOrNoneAwaitAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.SingleOrNoneAwaitWithCancellationAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static Funcky.Extensions.AsyncEnumerableExtensions.SlidingWindow(this System.Collections.Generic.IAsyncEnumerable! source, int width) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Split(this System.Collections.Generic.IAsyncEnumerable! source, TSource separator, System.Collections.Generic.IEqualityComparer! comparer, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Split(this System.Collections.Generic.IAsyncEnumerable! source, TSource separator) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Split(this System.Collections.Generic.IAsyncEnumerable! source, TSource separator, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.TakeEvery(this System.Collections.Generic.IAsyncEnumerable! source, int interval) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.Transpose(this System.Collections.Generic.IEnumerable!>! source) -> System.Collections.Generic.IAsyncEnumerable!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Traverse(this System.Collections.Generic.IAsyncEnumerable! source, System.Func!>! selector) -> System.Lazy!>! +static Funcky.Extensions.AsyncEnumerableExtensions.Traverse(this System.Collections.Generic.IAsyncEnumerable! source, System.Func!>! selector) -> Funcky.Monads.Reader!>! +static Funcky.Extensions.AsyncEnumerableExtensions.TraverseAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.TraverseAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.TraverseAsync(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>> +static Funcky.Extensions.AsyncEnumerableExtensions.WhereNotNull(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereNotNull(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelect(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelect(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelect(this System.Collections.Generic.IAsyncEnumerable>! source) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwait(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation(this System.Collections.Generic.IAsyncEnumerable! source, System.Func>>! selector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.WithFirst(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Extensions.AsyncEnumerableExtensions.WithIndex(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Extensions.AsyncEnumerableExtensions.WithLast(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Extensions.AsyncEnumerableExtensions.WithPrevious(this System.Collections.Generic.IAsyncEnumerable! source) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Extensions.AsyncEnumerableExtensions.ZipLongest(this System.Collections.Generic.IAsyncEnumerable! left, System.Collections.Generic.IAsyncEnumerable! right, System.Func, TResult>! resultSelector) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Extensions.AsyncEnumerableExtensions.ZipLongest(this System.Collections.Generic.IAsyncEnumerable! left, System.Collections.Generic.IAsyncEnumerable! right) -> System.Collections.Generic.IAsyncEnumerable>! static Funcky.Extensions.DictionaryExtensions.GetValueOrNone(this System.Collections.Generic.IDictionary! dictionary, TKey key) -> Funcky.Monads.Option static Funcky.Extensions.DictionaryExtensions.GetValueOrNone(this System.Collections.Generic.IReadOnlyDictionary! dictionary, TKey readOnlyKey) -> Funcky.Monads.Option +static Funcky.Extensions.DictionaryExtensions.RemoveOrNone(this System.Collections.Generic.IDictionary! dictionary, TKey key) -> Funcky.Monads.Option static Funcky.Extensions.EitherPartitions.Create(System.Collections.Generic.IReadOnlyList! left, System.Collections.Generic.IReadOnlyList! right) -> Funcky.Extensions.EitherPartitions static Funcky.Extensions.EnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! elementSelector, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.AdjacentGroupBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! elementSelector, System.Func!, TResult>! resultSelector, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IEnumerable! @@ -314,7 +570,6 @@ static Funcky.Extensions.EnumerableExtensions.Sequence(this Syst static Funcky.Extensions.EnumerableExtensions.Sequence(this System.Collections.Generic.IEnumerable>! source) -> Funcky.Monads.Option!> static Funcky.Extensions.EnumerableExtensions.Sequence(this System.Collections.Generic.IEnumerable>! source) -> Funcky.Monads.Result!> static Funcky.Extensions.EnumerableExtensions.Sequence(this System.Collections.Generic.IEnumerable!>! sequence) -> System.Lazy!>! -static Funcky.Extensions.EnumerableExtensions.Shuffle(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.IReadOnlyList! static Funcky.Extensions.EnumerableExtensions.Shuffle(this System.Collections.Generic.IEnumerable! source, System.Random! random) -> System.Collections.Generic.IReadOnlyList! static Funcky.Extensions.EnumerableExtensions.SingleOrNone(this System.Collections.Generic.IEnumerable! source) -> Funcky.Monads.Option static Funcky.Extensions.EnumerableExtensions.SingleOrNone(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> Funcky.Monads.Option @@ -324,6 +579,7 @@ static Funcky.Extensions.EnumerableExtensions.Split(this System.Collect static Funcky.Extensions.EnumerableExtensions.Split(this System.Collections.Generic.IEnumerable! source, TSource separator, System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.IEnumerable!>! static Funcky.Extensions.EnumerableExtensions.TakeEvery(this System.Collections.Generic.IEnumerable! source, int interval) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.Transpose(this System.Collections.Generic.IEnumerable!>! source) -> System.Collections.Generic.IEnumerable!>! +static Funcky.Extensions.EnumerableExtensions.Traverse(this System.Collections.Generic.IEnumerable! source, System.Func!>! selector) -> System.Lazy!>! static Funcky.Extensions.EnumerableExtensions.Traverse(this System.Collections.Generic.IEnumerable! source, System.Func!>! selector) -> Funcky.Monads.Reader!>! static Funcky.Extensions.EnumerableExtensions.Traverse(this System.Collections.Generic.IEnumerable! source, System.Func>! selector) -> Funcky.Monads.Option!> static Funcky.Extensions.EnumerableExtensions.Traverse(this System.Collections.Generic.IEnumerable! source, System.Func>! selector) -> Funcky.Monads.Either!> @@ -331,6 +587,7 @@ static Funcky.Extensions.EnumerableExtensions.Traverse(th static Funcky.Extensions.EnumerableExtensions.WhereNotNull(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.WhereNotNull(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.WhereSelect(this System.Collections.Generic.IEnumerable! source, System.Func>! selector) -> System.Collections.Generic.IEnumerable! +static Funcky.Extensions.EnumerableExtensions.WhereSelect(this System.Collections.Generic.IEnumerable! source, System.Func>! selector) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.WhereSelect(this System.Collections.Generic.IEnumerable>! source) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.WithFirst(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.IEnumerable>! static Funcky.Extensions.EnumerableExtensions.WithIndex(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.IEnumerable>! @@ -339,6 +596,58 @@ static Funcky.Extensions.EnumerableExtensions.WithPrevious(this System. static Funcky.Extensions.EnumerableExtensions.ZipLongest(this System.Collections.Generic.IEnumerable! left, System.Collections.Generic.IEnumerable! right, System.Func, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.EnumerableExtensions.ZipLongest(this System.Collections.Generic.IEnumerable! left, System.Collections.Generic.IEnumerable! right) -> System.Collections.Generic.IEnumerable>! static Funcky.Extensions.EnumeratorExtensions.MoveNextOrNone(this System.Collections.Generic.IEnumerator! enumerator) -> Funcky.Monads.Option +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2) -> System.Func! +static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2) -> System.Func! static Funcky.Extensions.FuncExtensions.Compose(this System.Func! f, System.Func! g) -> System.Func! static Funcky.Extensions.FuncExtensions.Compose(this System.Func! f, System.Func! g) -> System.Func! static Funcky.Extensions.FuncExtensions.Curry(this System.Func! function) -> System.Func!>!>!>!>!>!>!>! @@ -365,241 +674,260 @@ static Funcky.Extensions.FuncExtensions.Uncurry(this System.Fun static Funcky.Extensions.HttpHeadersExtensions.GetValuesOrNone(this System.Net.Http.Headers.HttpHeaders! headers, string! name) -> Funcky.Monads.Option!> static Funcky.Extensions.HttpHeadersNonValidatedExtensions.GetValuesOrNone(this System.Net.Http.Headers.HttpHeadersNonValidated headers, string! headerName) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option +static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ImmutableListExtensions.IndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option +static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ImmutableListExtensions.LastIndexOfOrNone(System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.FindIndexOrNone(this System.Collections.Generic.List! list, int startIndex, int count, System.Predicate! match) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.FindIndexOrNone(this System.Collections.Generic.List! list, int startIndex, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.JsonSerializerOptionsExtensions.GetTypeInfoOrNone(this System.Text.Json.JsonSerializerOptions! options, System.Type! type) -> Funcky.Monads.Option static Funcky.Extensions.ListExtensions.FindIndexOrNone(this System.Collections.Generic.List! list, System.Predicate! match) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.FindLastIndexOrNone(this System.Collections.Generic.List! list, int startIndex, int count, System.Predicate! match) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.FindLastIndexOrNone(this System.Collections.Generic.List! list, int startIndex, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.FindIndexOrNone(this System.Collections.Generic.List! list, int startIndex, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.FindIndexOrNone(this System.Collections.Generic.List! list, int startIndex, int count, System.Predicate! match) -> Funcky.Monads.Option static Funcky.Extensions.ListExtensions.FindLastIndexOrNone(this System.Collections.Generic.List! list, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.FindLastIndexOrNone(this System.Collections.Generic.List! list, int startIndex, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.FindLastIndexOrNone(this System.Collections.Generic.List! list, int startIndex, int count, System.Predicate! match) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Generic.IList! list, TValue value) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option +static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option +static Funcky.Extensions.OrderedDictionaryExtensions.IndexOfOrNone(this System.Collections.Generic.OrderedDictionary! dictionary, TKey key) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseAssemblyNameInfoOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseAuthenticationHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseBooleanOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseBigIntegerOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseBooleanOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseBooleanOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseCacheControlHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseCharOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseContentDispositionHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseContentRangeHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateOnlyOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOffsetOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDateTimeOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDecimalOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseDoubleOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseEntityTagHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, System.Type! enumType) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, System.Type! enumType, bool ignoreCase) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this System.ReadOnlySpan candidate, System.Type! enumType) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this System.ReadOnlySpan candidate, System.Type! enumType, bool ignoreCase) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, bool ignoreCase) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, System.Type! enumType) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, System.Type! enumType, bool ignoreCase) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this System.ReadOnlySpan candidate, bool ignoreCase) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseEnumOrNone(this string! candidate, bool ignoreCase) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this string? candidate, string? format) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this string? candidate, string? format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this string? candidate, string?[]? formats) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this string? candidate, string? format, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOffsetOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider, System.Globalization.DateTimeStyles styles) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this string? candidate, string? format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactDateTimeOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactGuidOrNone(this string? candidate, string? format) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactGuidOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactGuidOrNone(this string? candidate, string? format) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this string? candidate, string? format) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this string? candidate, string? format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this string? candidate, string?[]? formats) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this string? candidate, string? format, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this string? candidate, string? format, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this string? candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, string?[]? formats, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseExactTimeSpanOrNone(this System.ReadOnlySpan candidate, System.ReadOnlySpan format, System.IFormatProvider? formatProvider, System.Globalization.TimeSpanStyles styles) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseGuidOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPAddressOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPAddressOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPAddressOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPEndPointOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPEndPointOrNone(this string! candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPNetworkOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPNetworkOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseIPNetworkOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt16OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt32OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPAddressOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPAddressOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPEndPointOrNone(this string! candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPEndPointOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseInt64OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseMediaTypeHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseMediaTypeWithQualityHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseNameValueHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseNameValueWithParametersHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseNumberOrNone(this string! value, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseNumberOrNone(this System.ReadOnlySpan value, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseOrNone(this string? value, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseNumberOrNone(this string! value, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseOrNone(this System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseOrNone(this System.ReadOnlySpan value, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseOrNone(this string? value, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseProductHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseProductInfoHeaderValueOrNone(this string! candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseRangeConditionHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseRangeHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseRetryConditionHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSByteOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseSingleOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseStringWithQualityHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this string? candidate, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseTimeOnlyOrNone(this string? candidate, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseTimeSpanOrNone(this string? candidate, System.IFormatProvider? formatProvider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTransferCodingHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTransferCodingWithQualityHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseTypeNameOrNone(this System.ReadOnlySpan candidate, System.Reflection.Metadata.TypeNameParseOptions? options = null) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt16OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt32OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this System.ReadOnlySpan candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseVersionOrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseUInt64OrNone(this string? candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseVersionOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseVersionOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseVersionOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseViaHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.ParseExtensions.ParseWarningHeaderValueOrNone(this string? candidate) -> Funcky.Monads.Option static Funcky.Extensions.Partitions.Create(System.Collections.Generic.IReadOnlyList! true, System.Collections.Generic.IReadOnlyList! false) -> Funcky.Extensions.Partitions static Funcky.Extensions.PriorityQueueExtensions.DequeueOrNone(this System.Collections.Generic.PriorityQueue! priorityQueue) -> Funcky.Monads.Option<(TElement Element, TPriority Priority)> static Funcky.Extensions.PriorityQueueExtensions.PeekOrNone(this System.Collections.Generic.PriorityQueue! priorityQueue) -> Funcky.Monads.Option<(TElement Element, TPriority Priority)> -static Funcky.Extensions.QueryableExtensions.ElementAtOrNone(this System.Linq.IQueryable! source, int index) -> Funcky.Monads.Option static Funcky.Extensions.QueryableExtensions.ElementAtOrNone(this System.Linq.IQueryable! source, System.Index index) -> Funcky.Monads.Option +static Funcky.Extensions.QueryableExtensions.ElementAtOrNone(this System.Linq.IQueryable! source, int index) -> Funcky.Monads.Option static Funcky.Extensions.QueryableExtensions.FirstOrNone(this System.Linq.IQueryable! source) -> Funcky.Monads.Option static Funcky.Extensions.QueryableExtensions.FirstOrNone(this System.Linq.IQueryable! source, System.Linq.Expressions.Expression!>! predicate) -> Funcky.Monads.Option static Funcky.Extensions.QueryableExtensions.LastOrNone(this System.Linq.IQueryable! source) -> Funcky.Monads.Option @@ -627,15 +955,15 @@ static Funcky.Extensions.StringExtensions.IndexOfAnyOrNone(this string! haystack static Funcky.Extensions.StringExtensions.IndexOfAnyOrNone(this string! haystack, char[]! anyOf, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfAnyOrNone(this string! haystack, char[]! anyOf, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, char value) -> Funcky.Monads.Option +static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, char value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, char value, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, char value, int startIndex, int count) -> Funcky.Monads.Option -static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, char value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value) -> Funcky.Monads.Option +static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, int startIndex) -> Funcky.Monads.Option +static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, int startIndex, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, int startIndex, int count, System.StringComparison comparisonType) -> Funcky.Monads.Option -static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, int startIndex, System.StringComparison comparisonType) -> Funcky.Monads.Option -static Funcky.Extensions.StringExtensions.IndexOfOrNone(this string! haystack, string! value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfAnyOrNone(this string! haystack, char[]! anyOf) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfAnyOrNone(this string! haystack, char[]! anyOf, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfAnyOrNone(this string! haystack, char[]! anyOf, int startIndex, int count) -> Funcky.Monads.Option @@ -643,11 +971,11 @@ static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystac static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, char value, int startIndex) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, char value, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value) -> Funcky.Monads.Option +static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, int startIndex) -> Funcky.Monads.Option +static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, int startIndex, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, int startIndex, int count) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, int startIndex, int count, System.StringComparison comparisonType) -> Funcky.Monads.Option -static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, int startIndex, System.StringComparison comparisonType) -> Funcky.Monads.Option -static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystack, string! value, System.StringComparison comparisonType) -> Funcky.Monads.Option static Funcky.Extensions.StringExtensions.SlidingWindow(this string! source, int width) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.StringExtensions.SplitLazy(this string! text, char separator) -> System.Collections.Generic.IEnumerable! static Funcky.Extensions.StringExtensions.SplitLazy(this string! text, params char[]! separators) -> System.Collections.Generic.IEnumerable! @@ -665,6 +993,58 @@ static Funcky.Functional.ActionToUnit(System.Action! action) -> static Funcky.Functional.ActionToUnit(System.Action! action) -> System.Func! static Funcky.Functional.All(params System.Func![]! predicates) -> System.Func! static Funcky.Functional.Any(params System.Func![]! predicates) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2) -> System.Func! +static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2) -> System.Func! static Funcky.Functional.Curry(System.Func! function) -> System.Func!>!>!>!>!>!>!>! static Funcky.Functional.Curry(System.Action! action) -> System.Func!>!>!>!>!>!>!>! static Funcky.Functional.Curry(System.Func! function) -> System.Func!>!>!>!>!>!>! @@ -698,6 +1078,7 @@ static Funcky.Functional.Flip(System.Func(System.Action! action) -> System.Action! static Funcky.Functional.Flip(System.Func! function) -> System.Func! static Funcky.Functional.Flip(System.Action! function) -> System.Action! +static Funcky.Functional.Fn(T value) -> T static Funcky.Functional.Identity(T value) -> T static Funcky.Functional.NoOperation() -> void static Funcky.Functional.NoOperation(T1 ω1, T2 ω2, T3 ω3, T4 ω4, T5 ω5, T6 ω6, T7 ω7, T8 ω8) -> void @@ -741,12 +1122,19 @@ static Funcky.Functional.UnitToAction(System.Func(System.Func! unitFunction) -> System.Action! static Funcky.Functional.UnitToAction(System.Func! unitFunction) -> System.Action! static Funcky.Functional.UnitToAction(System.Func! unitFunction) -> System.Action! -static Funcky.Monads.Either.implicit operator Funcky.Monads.Either(TRight right) -> Funcky.Monads.Either static Funcky.Monads.Either.Left(TLeft left) -> Funcky.Monads.Either +static Funcky.Monads.Either.Right(TRight right) -> Funcky.Monads.Either +static Funcky.Monads.Either.implicit operator Funcky.Monads.Either(TRight right) -> Funcky.Monads.Either static Funcky.Monads.Either.operator !=(Funcky.Monads.Either left, Funcky.Monads.Either right) -> bool static Funcky.Monads.Either.operator ==(Funcky.Monads.Either left, Funcky.Monads.Either right) -> bool -static Funcky.Monads.Either.Right(TRight right) -> Funcky.Monads.Either static Funcky.Monads.Either.Return(TRight right) -> Funcky.Monads.Either +static Funcky.Monads.EitherAsyncExtensions.Sequence(this Funcky.Monads.Either!> either) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.EitherAsyncExtensions.Sequence(this Funcky.Monads.Either!> either) -> System.Threading.Tasks.Task>! +static Funcky.Monads.EitherAsyncExtensions.Sequence(this Funcky.Monads.Either> either) -> System.Threading.Tasks.ValueTask> +static Funcky.Monads.EitherAsyncExtensions.Traverse(this Funcky.Monads.Either either, System.Func!>! selector) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.EitherAsyncExtensions.Traverse(this Funcky.Monads.Either either, System.Func!>! selector) -> System.Threading.Tasks.Task>! +static Funcky.Monads.EitherAsyncExtensions.Traverse(this Funcky.Monads.Either either, System.Func>! selector) -> System.Threading.Tasks.ValueTask> +static Funcky.Monads.EitherExtensions.Flatten(this Funcky.Monads.Either> either) -> Funcky.Monads.Either static Funcky.Monads.EitherExtensions.LeftOrNone(this Funcky.Monads.Either either) -> Funcky.Monads.Option static Funcky.Monads.EitherExtensions.RightOrNone(this Funcky.Monads.Either either) -> Funcky.Monads.Option static Funcky.Monads.EitherExtensions.Sequence(this Funcky.Monads.Either!> either) -> Funcky.Monads.Reader>! @@ -759,13 +1147,12 @@ static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Mon static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func>! selector) -> Funcky.Monads.Option> static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func>! selector) -> Funcky.Monads.Result> -static Funcky.Monads.EitherExtensions.Flatten(this Funcky.Monads.Either> either) -> Funcky.Monads.Either static Funcky.Monads.Lazy.FromFunc(System.Func! valueFactory) -> System.Lazy! static Funcky.Monads.Lazy.Return(T value) -> System.Lazy! +static Funcky.Monads.LazyExtensions.Flatten(this System.Lazy!>! lazy) -> System.Lazy! static Funcky.Monads.LazyExtensions.Select(this System.Lazy! lazy, System.Func! selector) -> System.Lazy! static Funcky.Monads.LazyExtensions.SelectMany(this System.Lazy! lazy, System.Func!>! selector, System.Func! resultSelector) -> System.Lazy! static Funcky.Monads.LazyExtensions.SelectMany(this System.Lazy! lazy, System.Func!>! selector) -> System.Lazy! -static Funcky.Monads.LazyExtensions.Flatten(this System.Lazy!>! lazy) -> System.Lazy! static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option static Funcky.Monads.Option.FromBoolean(bool boolean, System.Func! selector) -> Funcky.Monads.Option static Funcky.Monads.Option.FromBoolean(bool boolean, TItem item) -> Funcky.Monads.Option @@ -773,19 +1160,35 @@ static Funcky.Monads.Option.FromNullable(TItem? item) -> Funcky.Monads.Op static Funcky.Monads.Option.FromNullable(TItem? item) -> Funcky.Monads.Option static Funcky.Monads.Option.Return(TItem item) -> Funcky.Monads.Option static Funcky.Monads.Option.Some(TItem item) -> Funcky.Monads.Option -static Funcky.Monads.Option.implicit operator Funcky.Monads.Option(TItem item) -> Funcky.Monads.Option static Funcky.Monads.Option.None.get -> Funcky.Monads.Option +static Funcky.Monads.Option.implicit operator Funcky.Monads.Option(TItem item) -> Funcky.Monads.Option static Funcky.Monads.Option.operator !=(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool static Funcky.Monads.Option.operator <(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool static Funcky.Monads.Option.operator <=(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool static Funcky.Monads.Option.operator ==(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool static Funcky.Monads.Option.operator >(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool static Funcky.Monads.Option.operator >=(Funcky.Monads.Option left, Funcky.Monads.Option right) -> bool +static Funcky.Monads.OptionAsyncExtensions.ConfigureAwait(this Funcky.Monads.Option option, bool continueOnCapturedContext) -> Funcky.Monads.ConfiguredOptionTaskAwaitable +static Funcky.Monads.OptionAsyncExtensions.ConfigureAwait(this Funcky.Monads.Option option, bool continueOnCapturedContext) -> Funcky.Monads.ConfiguredOptionValueTaskAwaitable +static Funcky.Monads.OptionAsyncExtensions.ConfigureAwait(this Funcky.Monads.Option!> option, bool continueOnCapturedContext) -> Funcky.Monads.ConfiguredOptionTaskAwaitable +static Funcky.Monads.OptionAsyncExtensions.ConfigureAwait(this Funcky.Monads.Option> option, bool continueOnCapturedContext) -> Funcky.Monads.ConfiguredOptionValueTaskAwaitable +static Funcky.Monads.OptionAsyncExtensions.GetAwaiter(this Funcky.Monads.Option option) -> Funcky.Monads.OptionTaskAwaiter +static Funcky.Monads.OptionAsyncExtensions.GetAwaiter(this Funcky.Monads.Option option) -> Funcky.Monads.OptionValueTaskAwaiter +static Funcky.Monads.OptionAsyncExtensions.GetAwaiter(this Funcky.Monads.Option!> option) -> Funcky.Monads.OptionTaskAwaiter +static Funcky.Monads.OptionAsyncExtensions.GetAwaiter(this Funcky.Monads.Option> option) -> Funcky.Monads.OptionValueTaskAwaiter +static Funcky.Monads.OptionAsyncExtensions.Sequence(this Funcky.Monads.Option!> option) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.OptionAsyncExtensions.Sequence(this Funcky.Monads.Option!> option) -> System.Threading.Tasks.Task>! +static Funcky.Monads.OptionAsyncExtensions.Sequence(this Funcky.Monads.Option> option) -> System.Threading.Tasks.ValueTask> +static Funcky.Monads.OptionAsyncExtensions.ToAsyncEnumerable(this Funcky.Monads.Option option) -> System.Collections.Generic.IAsyncEnumerable! +static Funcky.Monads.OptionAsyncExtensions.Traverse(this Funcky.Monads.Option option, System.Func!>! selector) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.OptionAsyncExtensions.Traverse(this Funcky.Monads.Option option, System.Func!>! selector) -> System.Threading.Tasks.Task>! +static Funcky.Monads.OptionAsyncExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> System.Threading.Tasks.ValueTask> static Funcky.Monads.OptionComparer.Create(System.Collections.Generic.IComparer! comparer) -> System.Collections.Generic.Comparer>! static Funcky.Monads.OptionComparer.Create(System.Comparison! comparison) -> System.Collections.Generic.Comparer>! static Funcky.Monads.OptionComparer.Default.get -> System.Collections.Generic.Comparer>! static Funcky.Monads.OptionEqualityComparer.Create(System.Collections.Generic.IEqualityComparer! comparer) -> System.Collections.Generic.EqualityComparer>! static Funcky.Monads.OptionEqualityComparer.Default.get -> System.Collections.Generic.EqualityComparer>! +static Funcky.Monads.OptionExtensions.Flatten(this Funcky.Monads.Option> option) -> Funcky.Monads.Option static Funcky.Monads.OptionExtensions.Sequence(this Funcky.Monads.Option!> option) -> Funcky.Monads.Reader>! static Funcky.Monads.OptionExtensions.Sequence(this Funcky.Monads.Option> option) -> Funcky.Monads.Result> static Funcky.Monads.OptionExtensions.Sequence(this Funcky.Monads.Option!> option) -> System.Collections.Generic.IEnumerable>! @@ -800,20 +1203,26 @@ static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Opti static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Either> static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Result> -static Funcky.Monads.OptionExtensions.Flatten(this Funcky.Monads.Option> option) -> Funcky.Monads.Option static Funcky.Monads.Reader.FromAction(System.Action! action) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.FromFunc(System.Func! function) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.Return(TResult value) -> Funcky.Monads.Reader! +static Funcky.Monads.ReaderExtensions.Flatten(this Funcky.Monads.Reader!>! reader) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.Select(this Funcky.Monads.Reader! source, System.Func! selector) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.SelectMany(this Funcky.Monads.Reader! source, System.Func!>! selector, System.Func! resultSelector) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.SelectMany(this Funcky.Monads.Reader! source, System.Func!>! selector) -> Funcky.Monads.Reader! -static Funcky.Monads.ReaderExtensions.Flatten(this Funcky.Monads.Reader!>! reader) -> Funcky.Monads.Reader! static Funcky.Monads.Result.Ok(TValidResult result) -> Funcky.Monads.Result static Funcky.Monads.Result.Return(TValidResult result) -> Funcky.Monads.Result static Funcky.Monads.Result.Error(System.Exception! exception) -> Funcky.Monads.Result static Funcky.Monads.Result.implicit operator Funcky.Monads.Result(TValidResult result) -> Funcky.Monads.Result static Funcky.Monads.Result.operator !=(Funcky.Monads.Result left, Funcky.Monads.Result right) -> bool static Funcky.Monads.Result.operator ==(Funcky.Monads.Result left, Funcky.Monads.Result right) -> bool +static Funcky.Monads.ResultAsyncExtensions.Sequence(this Funcky.Monads.Result!> result) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.ResultAsyncExtensions.Sequence(this Funcky.Monads.Result!> result) -> System.Threading.Tasks.Task>! +static Funcky.Monads.ResultAsyncExtensions.Sequence(this Funcky.Monads.Result> result) -> System.Threading.Tasks.ValueTask> +static Funcky.Monads.ResultAsyncExtensions.Traverse(this Funcky.Monads.Result result, System.Func!>! selector) -> System.Collections.Generic.IAsyncEnumerable>! +static Funcky.Monads.ResultAsyncExtensions.Traverse(this Funcky.Monads.Result result, System.Func!>! selector) -> System.Threading.Tasks.Task>! +static Funcky.Monads.ResultAsyncExtensions.Traverse(this Funcky.Monads.Result result, System.Func>! selector) -> System.Threading.Tasks.ValueTask> +static Funcky.Monads.ResultExtensions.Flatten(this Funcky.Monads.Result> result) -> Funcky.Monads.Result static Funcky.Monads.ResultExtensions.Sequence(this Funcky.Monads.Result!> result) -> Funcky.Monads.Reader>! static Funcky.Monads.ResultExtensions.Sequence(this Funcky.Monads.Result> result) -> Funcky.Monads.Either> static Funcky.Monads.ResultExtensions.Sequence(this Funcky.Monads.Result> result) -> Funcky.Monads.Option> @@ -824,9 +1233,8 @@ static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Mona static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func>! selector) -> Funcky.Monads.Option> static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func>! selector) -> Funcky.Monads.Either> -static Funcky.Monads.ResultExtensions.Flatten(this Funcky.Monads.Result> result) -> Funcky.Monads.Result -static Funcky.Sequence.Concat(params System.Collections.Generic.IEnumerable![]! sources) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Concat(System.Collections.Generic.IEnumerable!>! sources) -> System.Collections.Generic.IEnumerable! +static Funcky.Sequence.Concat(params System.Collections.Generic.IEnumerable![]! sources) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Cycle(TResult element) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.CycleMaterialized(System.Collections.Generic.IReadOnlyCollection! source) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.CycleRange(System.Collections.Generic.IEnumerable! source) -> Funcky.IBuffer! @@ -834,19 +1242,19 @@ static Funcky.Sequence.FromNullable(TResult? element) -> System.Collect static Funcky.Sequence.FromNullable(TResult? element) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.RepeatMaterialized(System.Collections.Generic.IReadOnlyCollection! source, int count) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.RepeatRange(System.Collections.Generic.IEnumerable! source, int count) -> Funcky.IBuffer! -static Funcky.Sequence.Return(params TResult[]! elements) -> System.Collections.Generic.IReadOnlyList! static Funcky.Sequence.Return(TResult element) -> System.Collections.Generic.IReadOnlyList! +static Funcky.Sequence.Return(params TResult[]! elements) -> System.Collections.Generic.IReadOnlyList! static Funcky.Sequence.Successors(Funcky.Monads.Option first, System.Func>! successor) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Successors(Funcky.Monads.Option first, System.Func! successor) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Successors(TResult first, System.Func>! successor) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Successors(TResult first, System.Func! successor) -> System.Collections.Generic.IEnumerable! +static Funcky.Unit.Value.get -> Funcky.Unit static Funcky.Unit.operator !=(Funcky.Unit left, Funcky.Unit right) -> bool static Funcky.Unit.operator <(Funcky.Unit left, Funcky.Unit right) -> bool static Funcky.Unit.operator <=(Funcky.Unit left, Funcky.Unit right) -> bool static Funcky.Unit.operator ==(Funcky.Unit left, Funcky.Unit right) -> bool static Funcky.Unit.operator >(Funcky.Unit left, Funcky.Unit right) -> bool static Funcky.Unit.operator >=(Funcky.Unit left, Funcky.Unit right) -> bool -static Funcky.Unit.Value.get -> Funcky.Unit static Funcky.UpCast.From(System.Lazy! lazy) -> System.Lazy! static Funcky.UpCast.From(Funcky.Monads.Option option) -> Funcky.Monads.Option static Funcky.UpCast.From(Funcky.Monads.Either either) -> Funcky.Monads.Either diff --git a/Funcky/PublicAPI.Unshipped.txt b/Funcky/PublicAPI.Unshipped.txt index 52871fcaa..7dc5c5811 100644 --- a/Funcky/PublicAPI.Unshipped.txt +++ b/Funcky/PublicAPI.Unshipped.txt @@ -1,127 +1 @@ #nullable enable -Funcky.Extensions.JsonSerializerOptionsExtensions -Funcky.Extensions.OrderedDictionaryExtensions -Funcky.Monads.IEither -Funcky.Monads.IOption -static Funcky.Extensions.DictionaryExtensions.RemoveOrNone(this System.Collections.Generic.IDictionary! dictionary, TKey key) -> Funcky.Monads.Option -static Funcky.Extensions.EnumerableExtensions.Traverse(this System.Collections.Generic.IEnumerable! source, System.Func!>! selector) -> System.Lazy!>! -static Funcky.Extensions.EnumerableExtensions.WhereSelect(this System.Collections.Generic.IEnumerable! source, System.Func>! selector) -> System.Collections.Generic.IEnumerable! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2, T3 p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2, T3 p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, T2 p2, Funcky.Unit p3) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, Funcky.Unit p1, T2 p2) -> System.Func! -static Funcky.Extensions.FuncExtensions.Apply(this System.Func! func, T1 p1, Funcky.Unit p2) -> System.Func! -static Funcky.Extensions.JsonSerializerOptionsExtensions.GetTypeInfoOrNone(this System.Text.Json.JsonSerializerOptions! options, System.Type! type) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.IndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.ListExtensions.LastIndexOfOrNone(this System.Collections.Immutable.IImmutableList! list, TItem item, System.Collections.Generic.IEqualityComparer? equalityComparer) -> Funcky.Monads.Option -static Funcky.Extensions.OrderedDictionaryExtensions.IndexOfOrNone(this System.Collections.Generic.OrderedDictionary! dictionary, TKey key) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPNetworkOrNone(this string? candidate) -> Funcky.Monads.Option -static Funcky.Extensions.ParseExtensions.ParseIPNetworkOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4, T5 p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, T4 p4, Funcky.Unit p5) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3, T4 p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, T3 p3, Funcky.Unit p4) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, Funcky.Unit p2, T3 p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, Funcky.Unit p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2, T3 p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, Funcky.Unit p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2, T3 p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, T2 p2, Funcky.Unit p3) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, Funcky.Unit p1, T2 p2) -> System.Func! -static Funcky.Functional.Apply(System.Func! func, T1 p1, Funcky.Unit p2) -> System.Func! -static Funcky.Functional.Fn(T value) -> T diff --git a/Funcky/ValueTaskFactory.cs b/Funcky/ValueTaskFactory.cs new file mode 100644 index 000000000..e9935789c --- /dev/null +++ b/Funcky/ValueTaskFactory.cs @@ -0,0 +1,16 @@ +#if INTEGRATED_ASYNC +using System.Runtime.CompilerServices; + +namespace Funcky; + +internal static class ValueTaskFactory +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ValueTask ValueTaskFromResult(TResult result) + => new(result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ValueTask ValueTaskFromResult(TResult result, CancellationToken cancellationToken) + => new(result); +} +#endif diff --git a/changelog.md b/changelog.md index 422d3e90c..08f5421e9 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. Funcky adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Funcky 3.6.0 + +This update is mainly to update to .NET 10. + +* remove dependency to System.Linq.Async because it is integrated into .NET 10 +* Integrate Funcky.Async into Funcky, as the async extensions are now in the same assembly as the sync ones. + * This means that there is no longer a separate `Funcky.Async` package. + ## Funcky 3.5.1 | Funcky.Async 1.4.1 | Funcky.Xunit 2.1.1 | Funcky.Analyzers 1.4.1 This is a patch release which fixes vulnerability warnings of (direct and transitive) dependencies. diff --git a/global.json b/global.json index fe402e1f8..311edc218 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.200", + "version": "10.0.103", "rollForward": "feature" } } diff --git a/nupkg/Funcky.3.5.1.snupkg b/nupkg/Funcky.3.5.1.snupkg new file mode 100644 index 000000000..ba2b532c1 Binary files /dev/null and b/nupkg/Funcky.3.5.1.snupkg differ diff --git a/nupkg/Funcky.Async.1.4.1.snupkg b/nupkg/Funcky.Async.1.4.1.snupkg new file mode 100644 index 000000000..73df2f23f Binary files /dev/null and b/nupkg/Funcky.Async.1.4.1.snupkg differ diff --git a/nupkg/Funcky.Xunit.2.1.1.snupkg b/nupkg/Funcky.Xunit.2.1.1.snupkg new file mode 100644 index 000000000..80f1f1994 Binary files /dev/null and b/nupkg/Funcky.Xunit.2.1.1.snupkg differ diff --git a/nupkg/Funcky.Xunit.v3.1.0.0.snupkg b/nupkg/Funcky.Xunit.v3.1.0.0.snupkg new file mode 100644 index 000000000..9b71d21de Binary files /dev/null and b/nupkg/Funcky.Xunit.v3.1.0.0.snupkg differ