Skip to content

Commit

Permalink
Implement InspectEmpty on IEnumerable.
Browse files Browse the repository at this point in the history
When we implemented InspectNone, InspectLeft and InspectError, we kind of forgot this one.
  • Loading branch information
FreeApophis committed Dec 19, 2023
1 parent 7ba9b9c commit 1bd6df2
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma warning disable SA1010 // StyleCop support for collection expressions is missing

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

0 comments on commit 1bd6df2

Please sign in to comment.