Skip to content

Commit

Permalink
simplified either instantiation (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dirk-Peters authored Nov 15, 2021
1 parent 3e5c4f4 commit 9143943
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 53 deletions.
49 changes: 40 additions & 9 deletions MonadicBits/Either.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using System;
using System.Diagnostics.CodeAnalysis;
using static MonadicBits.Functional;
using MonadicBits.Either;

namespace MonadicBits
{
using static Functional;

public partial class Functional
{
public static Left<TLeft> Left<TLeft>(this TLeft value) => value;
public static Right<TRight> Right<TRight>(this TRight value) => value;
}

public readonly struct Either<TLeft, TRight>
{
private TLeft LeftInstance { get; }
Expand All @@ -24,22 +32,18 @@ private Either(TRight rightInstance)
IsRight = true;
}

public static Either<TLeft, TRight> Left(TLeft instance) => new Either<TLeft, TRight>(instance);

public static Either<TLeft, TRight> Right(TRight instance) => new Either<TLeft, TRight>(instance);

public Either<TLeft, TResult> Bind<TResult>([NotNull] Func<TRight, Either<TLeft, TResult>> mapping)
{
if (mapping == null) throw new ArgumentNullException(nameof(mapping));

return IsRight ? mapping(RightInstance) : Either<TLeft, TResult>.Left(LeftInstance);
return IsRight ? mapping(RightInstance) : LeftInstance.Left();
}

public Either<TResult, TRight> BindLeft<TResult>(Func<TLeft, Either<TResult, TRight>> mapping)
{
if (mapping == null) throw new ArgumentNullException(nameof(mapping));

return IsRight ? Either<TResult, TRight>.Right(RightInstance) : mapping(LeftInstance);
return IsRight ? RightInstance.Right() : mapping(LeftInstance);
}

public TResult Match<TResult>([NotNull] Func<TLeft, TResult> left, [NotNull] Func<TRight, TResult> right)
Expand All @@ -65,16 +69,43 @@ public Either<TLeft, TResult> Map<TResult>([NotNull] Func<TRight, TResult> mappi
{
if (mapping == null) throw new ArgumentNullException(nameof(mapping));

return Match(Either<TLeft, TResult>.Left, right => Either<TLeft, TResult>.Right(mapping(right)));
return Match<Either<TLeft, TResult>>(l => l.Left(), right => mapping(right).Right());
}

public Either<TResult, TRight> MapLeft<TResult>([NotNull] Func<TLeft, TResult> mapping)
{
if (mapping == null) throw new ArgumentNullException(nameof(mapping));

return Match(left => Either<TResult, TRight>.Left(mapping(left)), Either<TResult, TRight>.Right);
return Match<Either<TResult, TRight>>(left => mapping(left).Left(), r => r.Right());
}

public Maybe<TRight> ToMaybe() => Match(_ => Nothing, right => right.Just());

public static implicit operator Either<TLeft, TRight>(Left<TLeft> left) =>
new Either<TLeft, TRight>(left.Value);

public static implicit operator Either<TLeft, TRight>(Right<TRight> right) =>
new Either<TLeft, TRight>(right.Value);
}

namespace Either
{
public readonly struct Left<T>
{
public T Value { get; }

public Left(T value) => Value = value;

public static implicit operator Left<T>(T value) => new Left<T>(value);
}

public readonly struct Right<T>
{
public T Value { get; }

public Right(T value) => Value = value;

public static implicit operator Right<T>(T value) => new Right<T>(value);
}
}
}
4 changes: 2 additions & 2 deletions MonadicBits/EitherAsyncExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static Task<Either<TLeft, TResult>> MapAsync<TLeft, TRight, TResult>(
this Either<TLeft, TRight> either, Func<TRight, Task<TResult>> mapping,
bool continueOnCapturedContext = false) =>
either.Map(mapping).Match(
left => Task.FromResult(left.Left<TLeft, TResult>()),
left => Task.FromResult<Either<TLeft,TResult>>(left.Left()),
async task => (await task.ConfigureAwait(continueOnCapturedContext)).Right<TLeft, TResult>());

public static Task<Either<TResult, TRight>> MapLeftAsync<TLeft, TRight, TResult>(
Expand Down Expand Up @@ -48,7 +48,7 @@ public static Task<Either<TLeft, TResult>> BindAsync<TLeft, TRight, TResult>(
if (mapping == null) throw new ArgumentNullException(nameof(mapping));

return either.Match(
left => Task.FromResult(left.Left<TLeft, TResult>()),
left => Task.FromResult<Either<TLeft, TResult>>(left.Left()),
async right => await mapping(right).ConfigureAwait(continueOnCapturedContext));
}

Expand Down
12 changes: 6 additions & 6 deletions MonadicBits/EitherExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace MonadicBits
namespace MonadicBits
{
public static class EitherExtensions
{
public static Either<TLeft, TRight> Left<TLeft, TRight>(this TLeft left) =>
Either<TLeft, TRight>.Left(left);
public static Either<TLeft, TRight> Left<TLeft, TRight>(this TLeft value) =>
value.Left();

public static Either<TLeft, TRight> Right<TLeft, TRight>(this TRight right) =>
Either<TLeft, TRight>.Right(right);
public static Either<TLeft, TRight> Right<TLeft, TRight>(this TRight value) =>
value.Right();
}
}
}
15 changes: 15 additions & 0 deletions MonadicBits/Enumerable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace MonadicBits
{
public static class Enumerable
{
public static IEnumerable<T> Return<T>(T value)
{
yield return value;
}

public static IEnumerable<T> ToEnumerable<T>(this T @this) =>
Return(@this);
}
}
4 changes: 2 additions & 2 deletions MonadicBits/Maybe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private Maybe(T instance)
IsJust = true;
}

public Maybe<TResult> Bind<TResult>([NotNull] Func<T, Maybe<TResult>> mapping) =>
public Maybe<TResult> Bind<TResult>([NotNull] Func<T, Maybe<TResult>> mapping) =>
Match(mapping, () => Nothing);

public Maybe<TResult> Map<TResult>([NotNull] Func<T, TResult> mapping)
Expand Down Expand Up @@ -52,7 +52,7 @@ public void Match([NotNull] Action<T> just, [NotNull] Action nothing)
}

public Either<TLeft, T> ToEither<TLeft>([NotNull] TLeft left) =>
IsJust ? Either<TLeft, T>.Right(Instance) : Either<TLeft, T>.Left(left);
Match<Either<TLeft, T>>(i => i.Right(), () => left.Left());

public static implicit operator Maybe<T>(Nothing _) => new Maybe<T>();

Expand Down
26 changes: 21 additions & 5 deletions MonadicBits/MaybeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,39 @@ public static class MaybeExtensions
{
public static Maybe<T> Just<T>(this T value)
{
if (value == null) throw new ArgumentNullException(
nameof(value),
"cannot create just from null value");
if (value == null)
throw new ArgumentNullException(
nameof(value),
"cannot create just from null value");
return value;
}

public static Maybe<T> JustNotNull<T>(this T value) =>
value == null ? Nothing : value.Just();
value?.Just() ?? Nothing;

public static Maybe<T> ToMaybe<T>(this T? source) where T : struct =>
source?.Just() ?? Nothing;

public static T? ToNullable<T>(this Maybe<T> source) where T : struct =>
source.Match<T?>(j => j, () => null);

public static Maybe<T> Or<T>(this Maybe<T> source, Func<Maybe<T>> alternative) =>
source.Match(j => j, alternative);

public static Maybe<T> FirstOrNothing<T>(this IEnumerable<T> source) =>
source.Select(value => value.Just()).DefaultIfEmpty(Nothing).First();

public static Maybe<T> FirstOrNothing<T>(this IEnumerable<T> source, Func<T, bool> predicate) =>
source.Where(predicate).FirstOrNothing();

public static Maybe<T> JustWhen<T>(this T value, Func<T, bool> predicate)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

return predicate(value) ? value.Just() : Nothing;
}

public static IEnumerable<T> ToEnumerable<T>(this Maybe<T> source) =>
source.Match(Enumerable.Return, System.Linq.Enumerable.Empty<T>);
}
}
44 changes: 22 additions & 22 deletions MonadicBitsTests/EitherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,138 +10,138 @@ public static class EitherTests
public static void Right_creates_either_with_right_value()
{
const string input = "Test";
Either<string, string>.Right(input).Match(Assert.Fail, s => Assert.AreEqual(input, s));
TestMonads.Right(input).Match(Assert.Fail, s => Assert.AreEqual(input, s));
}

[Test]
public static void Left_creates_either_with_left_value()
{
const string input = "Test";
Either<string, string>.Left(input).Match(s => Assert.AreEqual(input, s), Assert.Fail);
TestMonads.Left(input).Match(s => Assert.AreEqual(input, s), Assert.Fail);
}

[Test]
public static void Match_with_null_left_action_throws_exception() =>
Assert.Throws<ArgumentNullException>(() => "Test".Left<string, string>().Match(null, _ => { }));
Assert.Throws<ArgumentNullException>(() => TestMonads.Left("Test").Match(null, _ => { }));

[Test]
public static void Match_with_null_right_action_throws_exception() =>
Assert.Throws<ArgumentNullException>(() => "Test".Left<string, string>().Match(_ => { }, null));
Assert.Throws<ArgumentNullException>(() => TestMonads.Left("Test").Match(_ => { }, null));

[Test]
public static void Match_with_null_left_func_throws_exception() =>
Assert.Throws<ArgumentNullException>(() => "Test".Left<string, string>().Match(null, _ => "Right"));
Assert.Throws<ArgumentNullException>(() => TestMonads.Left("Test").Match(null, _ => "Right"));

[Test]
public static void Match_with_null_right_func_throws_exception() =>
Assert.Throws<ArgumentNullException>(() => "Test".Left<string, string>().Match(_ => "Left", null));
Assert.Throws<ArgumentNullException>(() => TestMonads.Left("Test").Match(_ => "Left", null));

[Test]
public static void Match_right_either_returns_right_value()
{
const string value = "Test";
var result = value.Right<string, string>().Match(_ => "Left", s => s);
var result = TestMonads.Right(value).Match(_ => "Left", s => s);
Assert.AreEqual(value, result);
}

[Test]
public static void Match_left_either_returns_left_value()
{
const string value = "Test";
var result = value.Left<string, string>().Match(s => s, _ => "Right");
var result = TestMonads.Left(value).Match(s => s, _ => "Right");
Assert.AreEqual(value, result);
}

[Test]
public static void Map_right_either_with_null_mapping_throws_exception() =>
Assert.Throws<ArgumentNullException>(() => "Test".Right<string, string>().Map((Func<string, string>) null));
Assert.Throws<ArgumentNullException>(() => TestMonads.Right("Test").Map((Func<string, string>)null));

[Test]
public static void Map_right_either_returns_either_with_new_type_and_value()
{
const int mappedValue = 42;
"Test".Right<string, string>().Map(_ => mappedValue)
TestMonads.Right("Test").Map(_ => mappedValue)
.Match(Assert.Fail, i => Assert.AreEqual(mappedValue, i));
}

[Test]
public static void Map_left_either_returns_same_left_either()
{
const string value = "Test";
value.Left<string, string>().Map(_ => 42).Match(s => Assert.AreEqual(value, s), _ => Assert.Fail());
TestMonads.Left(value).Map(_ => 42).Match(s => Assert.AreEqual(value, s), _ => Assert.Fail());
}

[Test]
public static void MapLeft_left_either_with_null_mapping_throws_exception() =>
Assert.Throws<ArgumentNullException>(() =>
"Test".Left<string, string>().MapLeft((Func<string, string>) null));
TestMonads.Left("Test").MapLeft((Func<string, string>)null));

[Test]
public static void MapLeft_left_either_returns_either_with_new_type_and_value()
{
const int mappedValue = 42;
"Test".Left<string, string>().MapLeft(_ => mappedValue)
TestMonads.Left("Test").MapLeft(_ => mappedValue)
.Match(i => Assert.AreEqual(mappedValue, i), Assert.Fail);
}

[Test]
public static void MapLeft_right_either_returns_same_right_either()
{
const string value = "Test";
value.Right<string, string>().MapLeft(_ => 42).Match(_ => Assert.Fail(), s => Assert.AreEqual(value, s));
TestMonads.Right(value).MapLeft(_ => 42).Match(_ => Assert.Fail(), s => Assert.AreEqual(value, s));
}

[Test]
public static void Bind_right_either_with_null_mapping_throws_exception() =>
Assert.Throws<ArgumentNullException>(() =>
"Test".Right<string, string>().Bind((Func<string, Either<string, string>>) null));
TestMonads.Right("Test").Bind((Func<string, Either<string, string>>)null));

[Test]
public static void Bind_right_either_to_method_returns_either_with_new_type_and_value()
{
const int bindValue = 42;
"Test".Right<string, string>().Bind(_ => bindValue.Right<string, int>())
TestMonads.Right("Test").Bind(_ => bindValue.Right<string, int>())
.Match(Assert.Fail, i => Assert.AreEqual(bindValue, i));
}

[Test]
public static void Bind_left_either_to_method_returns_same_left_either()
{
const string value = "Test";
value.Left<string, string>().Bind(_ => 42.Right<string, int>())
TestMonads.Left(value).Bind(_ => 42.Right<string, int>())
.Match(s => Assert.AreEqual(value, s), _ => Assert.Fail());
}

[Test]
public static void BindLeft_left_either_with_null_mapping_throws_exception() =>
Assert.Throws<ArgumentNullException>(() =>
"Test".Left<string, string>().BindLeft((Func<string, Either<string, string>>) null));
TestMonads.Left("Test").BindLeft((Func<string, Either<string, string>>)null));

[Test]
public static void BindLeft_left_either_to_method_returns_either_with_new_type_and_value()
{
const int bindValue = 42;
"Test".Left<string, string>().BindLeft(_ => bindValue.Left<int, string>())
TestMonads.Left("Test").BindLeft(_ => bindValue.Left<int, string>())
.Match(i => Assert.AreEqual(bindValue, i), Assert.Fail);
}

[Test]
public static void BindLeft_right_either_to_method_returns_same_right_either()
{
const string value = "Test";
value.Right<string, string>().BindLeft(_ => 42.Left<int, string>())
TestMonads.Right(value).BindLeft(_ => 42.Left<int, string>())
.Match(_ => Assert.Fail(), s => Assert.AreEqual(value, s));
}

[Test]
public static void Left_to_maybe_returns_empty_maybe() =>
"Test".Left<string, string>().ToMaybe().Match(Assert.Fail, Assert.Pass);
TestMonads.Left("Test").ToMaybe().Match(Assert.Fail, Assert.Pass);

[Test]
public static void Right_to_maybe_returns_maybe_with_value()
{
const string value = "Test";
value.Right<string, string>().ToMaybe().Match(s => Assert.AreEqual(value, s), Assert.Fail);
TestMonads.Right(value).ToMaybe().Match(s => Assert.AreEqual(value, s), Assert.Fail);
}
}
}
12 changes: 12 additions & 0 deletions MonadicBitsTests/EnumerableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentAssertions;
using NUnit.Framework;

namespace MonadicBitsTests
{
public static class EnumerableTests
{
[Test]
public static void Elevation_creates_single_item_enumeration() =>
MonadicBits.Enumerable.Return(42).Should().BeEquivalentTo(new[] { 42 });
}
}
Loading

0 comments on commit 9143943

Please sign in to comment.