Skip to content

Commit

Permalink
Guards and error catching
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Louth committed Jul 16, 2021
1 parent 7644dc2 commit 652f38f
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 132 deletions.
72 changes: 72 additions & 0 deletions LanguageExt.Core/Catch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using LanguageExt.Common;

namespace LanguageExt
{
public static partial class Prelude
{
/// <summary>
/// Catch an error if the predicate matches
/// </summary>
public static CatchValue<A> matchError<A>(Func<Error, bool> predicate, Func<Error, A> Fail) =>
new CatchValue<A>(predicate, Fail);

/// <summary>
/// Catch an error if the predicate matches
/// </summary>
public static CatchValue<A> matchError<A>(Func<Error, bool> predicate, A Fail) =>
matchError(predicate, _ => Fail);


/// <summary>
/// Catch an error if the error matches the argument provided
/// </summary>
public static CatchValue<A> match<A>(Error error, Func<Error, A> Fail) =>
matchError(e => e == error, Fail);

/// <summary>
/// Catch an error if the error matches the argument provided
/// </summary>
public static CatchValue<A> match<A>(Error error, A Fail) =>
matchError(e => e == error, _ => Fail);


/// <summary>
/// Catch an error if the error `Code` matches the `errorCode` argument provided
/// </summary>
public static CatchValue<A> match<A>(int errorCode, Func<Error, A> Fail) =>
matchError(e => e.Code == errorCode, Fail);

/// <summary>
/// Catch an error if the error `Code` matches the `errorCode` argument provided
/// </summary>
public static CatchValue<A> match<A>(int errorCode, A Fail) =>
matchError(e => e.Code == errorCode, _ => Fail);


/// <summary>
/// Catch an error if the error message matches the `errorText` argument provided
/// </summary>
public static CatchValue<A> match<A>(string errorText, Func<Error, A> Fail) =>
matchError(e => e.Message == errorText, Fail);

/// <summary>
/// Catch an error if the error message matches the `errorText` argument provided
/// </summary>
public static CatchValue<A> match<A>(string errorText, A Fail) =>
matchError(e => e.Message == errorText, _ => Fail);


/// <summary>
/// Catch an error if it's of a specific exception type
/// </summary>
public static CatchValue<A> match<A>(Func<Exception, bool> predicate, Func<Exception, A> Fail) =>
matchError(e => e.Exception.Map(predicate).IfNone(false), e => Fail(e.ToException()));

/// <summary>
/// Catch an error if it's of a specific exception type
/// </summary>
public static CatchValue<A> match<A>(Func<Exception, bool> predicate, A Fail) =>
matchError(e => e.Exception.Map(predicate).IfNone(false), e => Fail);
}
}
16 changes: 16 additions & 0 deletions LanguageExt.Core/DataTypes/List/Lst.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using static LanguageExt.Prelude;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Text;
using LanguageExt.TypeClasses;
using LanguageExt.ClassInstances;

Expand Down Expand Up @@ -256,6 +257,21 @@ public static IEnumerable<A> Intersperse<A>(this IEnumerable<A> ma, A value)
}
}

/// <summary>
/// Concact all strings into one
/// </summary>
[Pure]
public static string Concat(this IEnumerable<string> xs)
{
var sb = new StringBuilder();
foreach (var x in xs)
{
sb.Append(x);
}

return sb.ToString();
}

/// <summary>
/// Apply an IEnumerable of values to an IEnumerable of functions
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions LanguageExt.Core/Effects/Aff.Prelude.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public static Aff<A> Aff<A>(Func<ValueTask<A>> f) =>
public static Aff<A> SuccessAff<A>(A value) =>
LanguageExt.Aff<A>.Success(value);

/// <summary>
/// Construct an successful effect with a pure value
/// </summary>
/// <param name="value">Pure value to construct the monad with</param>
/// <typeparam name="A">Bound value type</typeparam>
/// <returns>Asynchronous IO monad that captures the pure value</returns>
[Pure, MethodImpl(AffOpt.mops)]
public static Aff<RT, A> SuccessAff<RT, A>(A value) where RT : struct, HasCancel<RT> =>
LanguageExt.Aff<RT, A>.Success(value);

/// <summary>
/// Construct a failed effect
/// </summary>
Expand All @@ -72,6 +82,16 @@ public static Aff<A> SuccessAff<A>(A value) =>
public static Aff<A> FailAff<A>(Error error) =>
LanguageExt.Aff<A>.Fail(error);

/// <summary>
/// Construct a failed effect
/// </summary>
/// <param name="error">Error that represents the failure</param>
/// <typeparam name="A">Bound value type</typeparam>
/// <returns>Synchronous IO monad that captures the failure</returns>
[Pure, MethodImpl(AffOpt.mops)]
public static Aff<RT, A> FailAff<RT, A>(Error error) where RT : struct, HasCancel<RT> =>
LanguageExt.Aff<RT, A>.Fail(error);

/// <summary>
/// Create a new local context for the environment by mapping the outer environment and then
/// using the result as a new context when running the IO monad provided
Expand Down
167 changes: 90 additions & 77 deletions LanguageExt.Core/Effects/Aff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,123 +116,136 @@ public static Aff<Env, A> Fail(Error error) =>
[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, Aff<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, Aff<A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run().ConfigureAwait(false);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run().ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<A> ma, Aff<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run().ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));
async env =>
{
var ra = await ma.Run().ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, Eff<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(env);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(env);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Eff<Env, A> ma, Aff<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = ma.Run(env);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));
async env =>
{
var ra = ma.Run(env);
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Eff<A> ma, Aff<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = ma.Run();
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));
async env =>
{
var ra = ma.Run();
return ra.IsSucc
? ra
: await mb.Run(env).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, Eff<A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run();
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run();
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, EffCatch<A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(ra.Error);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(ra.Error);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, AffCatch<A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(ra.Error).ConfigureAwait(false);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(ra.Error).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, EffCatch<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(env, ra.Error);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: mb.Run(env, ra.Error);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, AffCatch<Env, A> mb) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env, ra.Error).ConfigureAwait(false);
}));
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: await mb.Run(env, ra.Error).ConfigureAwait(false);
}));

[Pure, MethodImpl(AffOpt.mops)]
public static Aff<Env, A> operator |(Aff<Env, A> ma, CatchValue<A> value) =>
new Aff<Env, A>(ThunkAsync<Env, A>.Lazy(
async env =>
{
var ra = await ma.Run(env).ConfigureAwait(false);
return ra.IsSucc
? ra
: value.Match(ra.Error)
? FinSucc(value.Value(ra.Error))
: ra;
}));

/// <summary>
/// Implicit conversion from pure Aff
Expand Down
27 changes: 27 additions & 0 deletions LanguageExt.Core/Effects/AffCatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

namespace LanguageExt
{
public readonly struct CatchValue<A>
{
public readonly Func<Error, bool> Match;
public readonly Func<Error, A> Value;

public CatchValue(Func<Error, bool> match, Func<Error, A> value) =>
(Match, Value) = (match, value);
}

public readonly struct AffCatch<A>
{
internal readonly Func<Error, Aff<A>> fail;
Expand All @@ -20,6 +29,15 @@ public AffCatch(Func<Error, bool> predicate, Func<Error, Aff<A>> fail) :
public ValueTask<Fin<A>> Run(Error error) =>
fail(error).Run();

public static AffCatch<A> operator |(CatchValue<A> ma, AffCatch<A> mb) =>
new AffCatch<A>(e => ma.Match(e) ? SuccessEff(ma.Value(e)) : mb.fail(e));

public static AffCatch<A> operator |(AffCatch<A> ma, CatchValue<A> mb) =>
new AffCatch<A>(e => ma.fail(e).MatchAff(Succ: SuccessAff,
Fail: e => mb.Match(e)
? SuccessAff(mb.Value(e))
: FailAff<A>(e)));

public static AffCatch<A> operator |(AffCatch<A> ma, AffCatch<A> mb) =>
new AffCatch<A>(e => ma.fail(e) | mb.fail(e));

Expand All @@ -44,6 +62,15 @@ public AffCatch(Func<Error, bool> predicate, Func<Error, Aff<RT, A>> fail) :
public ValueTask<Fin<A>> Run(RT env, Error error) =>
fail(error).Run(env);

public static AffCatch<RT, A> operator |(CatchValue<A> ma, AffCatch<RT, A> mb) =>
new AffCatch<RT, A>(e => ma.Match(e) ? SuccessEff(ma.Value(e)) : mb.fail(e));

public static AffCatch<RT, A> operator |(AffCatch<RT, A> ma, CatchValue<A> mb) =>
new AffCatch<RT, A>(e => ma.fail(e).MatchAff(Succ: SuccessAff<RT, A>,
Fail: e => mb.Match(e)
? SuccessAff<RT, A>(mb.Value(e))
: FailAff<RT, A>(e)));

public static AffCatch<RT, A> operator |(AffCatch<RT, A> ma, AffCatch<RT, A> mb) =>
new AffCatch<RT, A>(e => ma.fail(e) | mb.fail(e));

Expand Down
Loading

0 comments on commit 652f38f

Please sign in to comment.