Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement InspectEmpty on IEnumerable. #776

Merged
merged 2 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Funcky.Async.Test.TestUtilities;

namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions;

public sealed class InspectEmptyTest
{
[Fact]
public void InspectEmptyIsEnumeratedLazily()
{
var doNotEnumerate = new FailOnEnumerateAsyncSequence<object>();
_ = doNotEnumerate.InspectEmpty(NoOperation);
}

[Fact]
public async Task InspectEmptyExecutesAnInspectionFunctionOnMaterializationOnAnEmptyEnumerable()
{
var sideEffect = 0;
var asyncEnumerable = AsyncEnumerable.Empty<string>();

_ = 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);
}
}
41 changes: 41 additions & 0 deletions Funcky.Async/Extensions/AsyncEnumerableExtensions/InspectEmpty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Runtime.CompilerServices;

namespace Funcky.Extensions;

public static partial class AsyncEnumerableExtensions
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
/// <returns>returns an <see cref="IAsyncEnumerable{T}" /> with the side effect defined by action encoded in the async enumerable.</returns>
[Pure]
public static IAsyncEnumerable<TSource> InspectEmpty<TSource>(this IAsyncEnumerable<TSource> source, Action inspector)
=> InspectEmptyInternal(source, inspector);

private static async IAsyncEnumerable<TSource> InspectEmptyInternal<TSource>(
this IAsyncEnumerable<TSource> 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;
}
}
}
1 change: 1 addition & 0 deletions Funcky.Async/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#nullable enable
static Funcky.Extensions.AsyncEnumerableExtensions.InspectEmpty<TSource>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Action! inspector) -> System.Collections.Generic.IAsyncEnumerable<TSource>!
static Funcky.Monads.OptionAsyncExtensions.ToAsyncEnumerable<TItem>(this Funcky.Monads.Option<TItem> option) -> System.Collections.Generic.IAsyncEnumerable<TItem>!
36 changes: 36 additions & 0 deletions Funcky.Test/Extensions/EnumerableExtensions/InspectEmptyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma warning disable SA1010 // StyleCop support for collection expressions is missing
using Funcky.Test.TestUtils;

namespace Funcky.Test.Extensions.EnumerableExtensions;

public sealed class InspectEmptyTest
{
[Fact]
public void InspectEmptyIsEnumeratedLazily()
{
var doNotEnumerate = new FailOnEnumerationSequence<object>();
_ = doNotEnumerate.InspectEmpty(NoOperation);
}

[Fact]
public void InspectEmptyExecutesAnInspectionFunctionOnMaterializationOnAnEmptyEnumerable()
{
var sideEffect = 0;
IEnumerable<string> enumerable = [];

_ = enumerable.InspectEmpty(() => sideEffect = 1).Materialize();

Assert.Equal(1, sideEffect);
}

[Fact]
public void InspectEmptyExecutesNoInspectionFunctionOnMaterializationOnANonEmptyEnumerable()
{
var sideEffect = 0;
IEnumerable<string> enumerable = ["Hello", "World"];

_ = enumerable.InspectEmpty(() => sideEffect = 1).Materialize();

Assert.Equal(0, sideEffect);
}
}
31 changes: 31 additions & 0 deletions Funcky/Extensions/EnumerableExtensions/InspectEmpty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Funcky.Extensions;

public static partial class EnumerableExtensions
{
/// <summary>
/// An IEnumerable 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 IEnumerable.
/// The side effect will be executed when enumerating the result.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
/// <returns>returns an <see cref="IEnumerable{T}" /> with the side effect defined by action encoded in the enumerable.</returns>
[Pure]
public static IEnumerable<TSource> InspectEmpty<TSource>(this IEnumerable<TSource> source, Action inspector)
{
using var enumerator = source.GetEnumerator();

if (enumerator.MoveNext())
{
yield return enumerator.Current;
}
else
{
inspector();
yield break;
}

while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
1 change: 1 addition & 0 deletions Funcky/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Funcky.Monads.Result<TValidResult>.InspectError(System.Action<System.Exception!>
Funcky.Monads.Result<TValidResult>.OrElse(Funcky.Monads.Result<TValidResult> fallback) -> Funcky.Monads.Result<TValidResult>
Funcky.Monads.Result<TValidResult>.OrElse(System.Func<System.Exception!, Funcky.Monads.Result<TValidResult>>! fallback) -> Funcky.Monads.Result<TValidResult>
Funcky.UpCast<TResult>
static Funcky.Extensions.EnumerableExtensions.InspectEmpty<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Action! inspector) -> System.Collections.Generic.IEnumerable<TSource>!
static Funcky.Extensions.EnumeratorExtensions.MoveNextOrNone<T>(this System.Collections.Generic.IEnumerator<T>! enumerator) -> Funcky.Monads.Option<T>
static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan<byte> candidate, System.Globalization.NumberStyles style, System.IFormatProvider? provider) -> Funcky.Monads.Option<byte>
static Funcky.Extensions.ParseExtensions.ParseByteOrNone(this System.ReadOnlySpan<byte> candidate, System.IFormatProvider? provider) -> Funcky.Monads.Option<byte>
Expand Down