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

Created Conditional Type #179

Closed
wants to merge 1 commit into from
Closed
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
71 changes: 71 additions & 0 deletions LanguageExt.Core/DataTypes/Conditional/Conditional.Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LanguageExt.DataTypes.Conditional
{
public static class ConditionalExtensions
{
// X, X, X
public static Conditional<A, B> Cond<A, B>(this A val, Func<A, bool> condition, Func<A, B> returns) =>
condition(val)
? new Conditional<A, B>(val, returns(val))
: new Conditional<A, B>(val);

// Task<X>, X, X
public static async Task<Conditional<A, B>> Cond<A, B>(this Task<A> val, Func<A, bool> condition, Func<A, B> returns) =>
Cond(await val, condition, returns);

// X, Task<X>, X
public static async Task<Conditional<A, B>> Cond<A, B>(this A val, Func<A, Task<bool>> condition, Func<A, B> returns) =>
await condition(val)
? new Conditional<A, B>(val, returns(val))
: new Conditional<A, B>(val);

// Task<X>, Task<X>, X
public static async Task<Conditional<A, B>> Cond<A, B>(this Task<A> val, Func<A, Task<bool>> condition, Func<A, B> returns) =>
await Cond(await val, condition, returns);

// X, X, Task<X>
public static async Task<Conditional<A, B>> Cond<A, B>(this A val, Func<A, bool> condition, Func<A, Task<B>> returns) =>
condition(val)
? new Conditional<A, B>(val, await returns(val))
: new Conditional<A, B>(val);

// Task<X>, X, Task<X>
public static async Task<Conditional<A, B>> Cond<A, B>(this Task<A> val, Func<A, bool> condition, Func<A, Task<B>> returns) =>
await Cond(await val, condition, returns);

// X, Task<X>, Task<X>
public static async Task<Conditional<A, B>> Cond<A, B>(this A val, Func<A, Task<bool>> condition, Func<A, Task<B>> returns) =>
await condition(val)
? new Conditional<A, B>(val, await returns(val))
: new Conditional<A, B>(val);

// Task<X>, Task<X>, Task<X>
public static async Task<Conditional<A, B>> Cond<A, B>(this Task<A> val, Func<A, Task<bool>> condition, Func<A, Task<B>> returns) =>
await Cond(await val, condition, returns);


public static async Task<Conditional<A, B>> Cond<A, B>(this Task<Conditional<A, B>> continuation, Func<A, bool> condition, Func<A, B> returns) =>
(await continuation).Cond(condition, returns);


public static async Task<Conditional<A, B>> Cond<A, B>(this Task<Conditional<A, B>> continuation, Func<A, Task<bool>> condition, Func<A, B> returns) =>
await (await continuation).Cond(condition, returns);

public static async Task<Conditional<A, B>> Cond<A, B>(this Task<Conditional<A, B>> continuation, Func<A, bool> condition, Func<A, Task<B>> returns) =>
await (await continuation).Cond(condition, returns);

public static async Task<Conditional<A, B>> Cond<A, B>(this Task<Conditional<A, B>> continuation, Func<A, Task<bool>> condition, Func<A, Task<B>> returns) =>
await (await continuation).Cond(condition, returns);

public static async Task<B> Else<A, B>(this Task<Conditional<A, B>> continuation, Func<A, B> returns) =>
(await continuation).Else(returns);

public static async Task<B> Else<A, B>(this Task<Conditional<A, B>> continuation, Func<A, Task<B>> returns) =>
await (await continuation).Else(returns);
}
}
190 changes: 190 additions & 0 deletions LanguageExt.Core/DataTypes/Conditional/Conditional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LanguageExt.TypeClasses;
using static LanguageExt.Prelude;
using static System.Threading.Tasks.Task;

namespace LanguageExt.DataTypes.Conditional
{
/// <summary>
/// Cond is a construct used in many LISPs in the same manner as if/else if/else is used in C#
/// This class is designed to emulate the spirit of contruct in a fluent style.
/// The class implements the Optional Interface as unless an else clause is provided,
/// the structure may not have a result.
///
/// </summary>
/// <typeparam name="A"></typeparam>
/// <typeparam name="B"></typeparam>
public struct Conditional<A, B> : Optional<Conditional<A, B>, B>, IOptional
{
/// <summary>
/// This is set to Some when the predicate evaluates to true
/// </summary>
private readonly Option<B> result;

/// <summary>
/// This is the seed value which is passed to each predicate and result functions
/// </summary>
private readonly A subject;


internal Conditional(Some<A> subject)
{
this.subject = subject;
result = Option<B>.None;
}

internal Conditional(Some<A> subject, Some<B> result)
{
this.subject = subject;
this.result = result;
}


/// <summary>
/// If no other conditions were matched, then the result falls through to the else statement
/// </summary>
/// <param name="None">Function which produces the value if none of the previous predicates were true</param>
/// <returns></returns>
[Pure]
public B Else(Func<A, B> None) => map(this, continuation => continuation.result.Match(Some: identity, None: () => None(continuation.subject)));


/// <summary>
/// If no other conditions were matched, then the result falls through to the else statement
/// </summary>
/// <param name="None">Asynchronous function which produces the value if none of the previous predicates were true</param>
/// <returns></returns>
[Pure]
public Task<B> Else(Func<A, Task<B>> None) =>
map(this, continuation => continuation.result.Match(Some: FromResult, None: () => None(continuation.subject)));

/// <summary>
/// If no previous predicate has evaulated to true, then the current predicate is evaluated. If the predicate
/// matches, then the return function is invoked and the result set
/// </summary>
/// <param name="predicate"></param>
/// <param name="returns"></param>
/// <returns></returns>
public Conditional<A, B> Cond(Func<A, bool> predicate, Func<A, B> returns) =>
map(this, continuation =>
continuation.result.Match
(
Some: (_) => continuation,
None: () => predicate(continuation.subject)
? new Conditional<A, B>(continuation.subject, returns(continuation.subject))
: new Conditional<A, B>(continuation.subject)
));

/// <summary>
/// If no previous predicate has evaulated to true, then the current predicate is evaluated. If the predicate
/// matches, then the return function is invoked and the result set
/// </summary>
/// <param name="predicate"></param>
/// <param name="returns"></param>
/// <returns></returns>
public Task<Conditional<A, B>> Cond(Func<A, Task<bool>> predicate, Func<A, B> returns) =>
map(this, continuation =>
continuation.result.Match
(
Some: (_) => FromResult(continuation),
None: async () => await predicate(continuation.subject)
? new Conditional<A, B>(continuation.subject, returns(continuation.subject))
: new Conditional<A, B>(continuation.subject)
));


/// <summary>
/// If no previous predicate has evaulated to true, then the current predicate is evaluated. If the predicate
/// matches, then the return function is invoked and the result set
/// </summary>
/// <param name="predicate"></param>
/// <param name="returns"></param>
/// <returns></returns>
[Pure]
public Task<Conditional<A, B>> Cond(Func<A, bool> predicate, Func<A, Task<B>> returns) =>
map(this, continuation =>
continuation.result.Match
(
Some: _ => FromResult(continuation),
None: async () => predicate(continuation.subject)
? new Conditional<A, B>(continuation.subject, await returns(continuation.subject))
: new Conditional<A, B>(continuation.subject)
));

/// <summary>
/// If no previous predicate has evaulated to true, then the current predicate is evaluated. If the predicate
/// matches, then the return function is invoked and the result set
/// </summary>
/// <param name="predicate"></param>
/// <param name="returns"></param>
/// <returns></returns>
[Pure]
public Task<Conditional<A, B>> Cond(Func<A, Task<bool>> predicate, Func<A, Task<B>> returns) =>
map(this, continuation =>
continuation.result.Match
(
Some: (_) => FromResult(continuation),
None: async () => await predicate(continuation.subject)
? new Conditional<A, B>(continuation.subject, await returns(continuation.subject))
: new Conditional<A, B>(continuation.subject)
));



[Pure]
public C Match<C>(Func<B, C> Some, Func<C> None) => result.Match(Some: Some, None: None);

[Pure]
public C MatchUnsafe<C>(Func<B, C> Some, Func<C> None) => result.MatchUnsafe(Some: Some, None: None);


/// <summary>
/// Implicitely converts a conditional type into an option
/// </summary>
/// <param name="self"></param>
[Pure]
public static implicit operator Option<B>(Conditional<A, B> self) =>
self.result.Match
(
Some: Optional,
None: () => None
);



[Pure]
public bool IsSome => result.IsSome;

[Pure]
public bool IsNone => result.IsNone;

[Pure]
public R MatchUntyped<R>(Func<object, R> Some, Func<R> None) => result.Match(Some: (result) => Some(result), None: None);

[Pure]
public Type GetUnderlyingType() => result.GetUnderlyingType();

[Pure]
public bool IsUnsafe(Conditional<A, B> opt) => false;

[Pure]
bool Optional<Conditional<A, B>, B>.IsSome(Conditional<A, B> opt) => opt.IsSome;

[Pure]
bool Optional<Conditional<A, B>, B>.IsNone(Conditional<A, B> opt) => opt.IsNone;

[Pure]
public C Match<C>(Conditional<A, B> opt, Func<B, C> Some, Func<C> None) => opt.Match(Some: Some, None: None);

[Pure]
public C MatchUnsafe<C>(Conditional<A, B> opt, Func<B, C> Some, Func<C> None) => opt.MatchUnsafe<C>(Some: Some, None: None);

[Pure]
public Unit Match(Conditional<A, B> opt, Action<B> Some, Action None) => opt.result.Match(Some: Some, None: None);
}
}
2 changes: 2 additions & 0 deletions LanguageExt.Core/LanguageExt.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
<Compile Include="ClassInstances\Pred\Range.cs" />
<Compile Include="ClassInstances\Pred\String\Predicates.cs" />
<Compile Include="ClassInstances\TChar.cs" />
<Compile Include="DataTypes\Conditional\Conditional.cs" />
<Compile Include="DataTypes\Conditional\Conditional.Extensions.cs" />
<Compile Include="DataTypes\EitherUnsafe\EitherUnsafe.cs" />
<Compile Include="DataTypes\EitherUnsafe\EitherUnsafe.Trans.cs" />
<Compile Include="DataTypes\EitherUnsafe\EitherUnsafe.Extensions.cs" />
Expand Down
36 changes: 36 additions & 0 deletions LanguageExt.Tests/ConditionalTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LanguageExt.DataTypes.Conditional;
using Xunit;

namespace LanguageExtTests
{
public class ConditionalTests
{
[Fact]
public void SynchronousTests()
{
Assert.True(4.Cond(x => x == 4, (_) => true).Else((_) => false));
Assert.True(5.Cond(x => x == 4, (_) => true).Cond(x => x > 4, (_) => true).Else((_) => false));
Assert.False(3.Cond(x => x == 4, (_) => true).Cond(x => x > 4, (_) => true).Else((_) => false));
}

[Fact]
public async Task AsynchronousTests()
{
Assert.True(await (Task.FromResult(4).Cond(x => x == 4, (_) => true).Else((_) => false)));
Assert.False(await (Task.FromResult(3).Cond(x => x == 4, (_) => true).Cond(x => x > 4, (_) => true).Else((_) => false)));
Assert.True(await (4.Cond(x => Task.FromResult(x == 4), (_) => true).Else((_) => false)));
Assert.True(await (4.Cond(x => x == 4, (_) => Task.FromResult(true)).Else((_) => false)));
Assert.False(await 3.Cond(x => x == 4, (_) => true).Cond(x => x > 4, (_) => true).Else((_) => Task.FromResult(false)));
Assert.True(await (4.Cond(x => Task.FromResult(x == 4), (_) => Task.FromResult(true)).Else((_) => false)));
Assert.True(await (4.Cond(x => Task.FromResult(x == 4), (_) => Task.FromResult(true)).Else((_) => Task.FromResult(false))));
Assert.True(await (3.Cond(x => x == 4, (_) => true).Cond(x => Task.FromResult(x < 4), (_) => true).Else((_) => false)));
Assert.True(await (3.Cond(x => x == 4, (_) => true).Cond(x => Task.FromResult(x < 4), (_) => Task.FromResult(true)).Else((_) => false)));
Assert.True(await (3.Cond(x => x == 4, (_) => true).Cond(x => x < 4, (_) => Task.FromResult(true)).Else((_) => false)));
}
}
}
1 change: 1 addition & 0 deletions LanguageExt.Tests/LanguageExt.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="CompositionTests.cs" />
<Compile Include="ConditionalTests.cs" />
<Compile Include="HashMapTests.cs" />
<Compile Include="NewTypeTests.cs" />
<Compile Include="TaskTests.cs" />
Expand Down