Skip to content

Commit

Permalink
Merge pull request #32 from messerli-informatik-ag/individual-or-none
Browse files Browse the repository at this point in the history
Implement FirstOrNone, LastOrNone and SingleOrNone
  • Loading branch information
Ruben Schmidmeister authored Jun 8, 2020
2 parents ae41d7f + 2712056 commit 2bc1cea
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 3 deletions.
59 changes: 56 additions & 3 deletions Funcky.Test/EnumerableExtensionTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Funcky.Extensions;
using Funcky.Monads;
using Xunit;

using static Funcky.Functional;

namespace Funcky.Test
{
public class EnumerableExtensionTest
Expand Down Expand Up @@ -77,25 +80,75 @@ public void WhereSelectFiltersEmptyValues()
Assert.Equal(expectedResult, result);
}

[Theory]
[MemberData(nameof(ValueReferenceEnumerables))]
public void GivenAnValueEnumerableFirstLastOrNoneGivesTheCorrectOption(List<int> valueEnumerable, List<string> referenceEnumerable)
{
Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.FirstOrNone().Match(false, True));
Assert.Equal(ExpectedOptionValue(referenceEnumerable), referenceEnumerable.FirstOrNone().Match(false, True));

Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.LastOrNone().Match(false, True));
Assert.Equal(ExpectedOptionValue(referenceEnumerable), referenceEnumerable.LastOrNone().Match(false, True));
}

[Theory]
[MemberData(nameof(ValueReferenceEnumerables))]
public void GivenAnEnumerableSingleOrNoneGivesTheCorrectOption(List<int> valueEnumerable, List<string> referenceEnumerable)
{
ExpectedSingleOrNoneBehaviour(valueEnumerable, () => valueEnumerable.SingleOrNone().Match(false, True));
ExpectedSingleOrNoneBehaviour(valueEnumerable, () => referenceEnumerable.SingleOrNone().Match(false, True));
}

public static TheoryData<List<int>, List<string>> ValueReferenceEnumerables()
=> new TheoryData<List<int>, List<string>>
{
{ new List<int>(), new List<string>() },
{ new List<int> { 1 }, new List<string> { "a" } },
{ new List<int> { 1, 2, 3 }, new List<string> { "a", "b", "c" } },
};

private static Option<int> SquareEvenNumbers(int number)
=> IsEven(number) ? Option.Some(number * number) : Option<int>.None();

private static bool IsEven(int number) => number % 2 == 0;

private void AcceptIntegers(IEnumerable<int> values)
private static void AcceptIntegers(IEnumerable<int> values)
{
foreach (var value in values)
{
Assert.Equal(42, value);
}
}

private void AcceptUnits(IEnumerable<Unit> units)
private static void AcceptUnits(IEnumerable<Unit> units)
{
foreach (var unit in units)
{
Assert.Equal(default, unit);
}
}

private static bool ExpectedOptionValue<T>(List<T> valueEnumerable) =>
valueEnumerable.Count switch
{
0 => false,
_ => true,
};

private static void ExpectedSingleOrNoneBehaviour<T>(List<T> list, Func<bool> singleOrNone)
{
switch (list.Count)
{
case 0:
Assert.False(singleOrNone());
break;
case 1:
Assert.True(singleOrNone());
break;
default:
Assert.Throws<InvalidOperationException>(() => singleOrNone());
break;
}
}
}
}
63 changes: 63 additions & 0 deletions Funcky/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,68 @@ public static void Each<T>(this IEnumerable<T> elements, Action<T> action)
action(element);
}
}

/// <summary>
/// Returns the first element of a sequence as an <see cref="Option{T}" />, or a <see cref="Option{T}.None" /> value if the sequence contains no elements.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> FirstOrNone<TSource>(this IEnumerable<TSource> source)
where TSource : notnull =>
source
.Select(Option.Some)
.FirstOrDefault();

/// <summary>
/// Returns the first element of the sequence as an <see cref="Option{T}" /> that satisfies a condition or a <see cref="Option{T}.None" /> value if no such element is found.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> FirstOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
where TSource : notnull =>
source
.Where(predicate)
.Select(Option.Some)
.FirstOrDefault();

/// <summary>
/// Returns the last element of a sequence as an <see cref="Option{T}" />, or a <see cref="Option{T}.None" /> value if the sequence contains no elements.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> LastOrNone<TSource>(this IEnumerable<TSource> source)
where TSource : notnull =>
source
.Select(Option.Some)
.LastOrDefault();

/// <summary>
/// Returns the last element of a sequence that satisfies a condition as an <see cref="Option{T}" /> or a <see cref="Option{T}.None" /> value if no such element is found.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> LastOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
where TSource : notnull =>
source
.Where(predicate)
.Select(Option.Some)
.LastOrDefault();

/// <summary>
/// Returns the only element of a sequence as an <see cref="Option{T}" />, or a <see cref="Option{T}.None" /> value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> SingleOrNone<TSource>(this IEnumerable<TSource> source)
where TSource : notnull =>
source
.Select(Option.Some)
.SingleOrDefault();

/// <summary>
/// Returns the only element of a sequence that satisfies a specified condition as an <see cref="Option{T}" /> or a <see cref="Option{T}.None" /> value if no such element exists; this method throws an exception if more than one element satisfies the condition.
/// </summary>
/// <typeparam name="TSource">the inner type of the enumerable.</typeparam>
public static Option<TSource> SingleOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
where TSource : notnull =>
source
.Where(predicate)
.Select(Option.Some)
.SingleOrDefault();
}
}
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
* Add nullability annotations to everything except for `Monads.Reader`.
* Add a function for creating an `Option<T>` from a nullable value: `Option.From`.
* `Either.Match` now throws when called on an `Either` value created using `default(Either<L, R>)`.
* Add `True` and `False` functions to public API
* Match of `Result` Monad accepts actions
* Add `FirstOrNone`, `LastOrNone` and `SingleOrNone` extension functions

0 comments on commit 2bc1cea

Please sign in to comment.