First up, this is an awesome Library and it is making me love C# again.
I have noticed an issue when using nested Monads in LINQ from select statements.
In my case I'm nesting an Either inside a Task, however if the unwrapping is done in a single LINQ step a Bottom Exception occurs.
So given:
var failableTask = fun((Either<string, int> value) =>
Task.Run(() =>
{
Thread.Sleep(3000);
return value;
}));
The following will work as expected:
var result = await from a in failableTask(34)
from b in failableTask("This will work as expected")
select a + b;
However, if the first Either is in it's Left state a Bottom Exception occurs:
var result = await from a in failableTask("This will cause a Bottom Exception")
from b in failableTask(3)
select a + b;
If I unwrap the nested Monads separately it works as expected:
var result = await from a in failableTask("This will work as expected")
from b in failableTask(3)
select from a1 in a
from b1 in b
select a1 + b1;
I haven't yet explored what functions are called under the hood to debug this further just yet.
@chadedrupt Hi Chad, this is expected behaviour.
The reason it reports a BottomException is because it's doing a _transformer_ bind operation, which is a generalised system for monadic binding on two nested monads (in this case Task<Either<L, R>>). When the inner monad bind fails (which it will do because of the initial Left state), then so does the outer bind operation. The outer monad (Task<...>) isn't aware of the Left type on the Either so it can't give you a useful exception, so it fails with BottomException because the Task is now in a failed state.
So essentially you're getting a general failure response from the Task not the Either.
You should consider Try, TryAsync, TryOptionAsync for these kinds of operations. This very question was asked yesterday, below is an example from my response:
You may want to use Try<A> and TryAsync<A> it captures exceptions and removes the need for nesting monads. You can create methods that return Try<A> and easily make them asynchronous by calling ToAsync() or calling any of the Async variant functions like MatchAsync, MapAsync, etc.
```c#
static Try
user.ToString().Length;
static Try<UserMapping> createUserMapping2(ADUser user) => () =>
UserMapping.New(user.ToString() + " mapped");
public TryAsync<int> Issue207_5() =>
from us in TryAsync<ADUser>(() => throw new Exception("fail"))
from mu in createUserMapping2(us).ToAsync()
from id in addUser2(mu).ToAsync()
select id;
public async Task<int> Test2()
{
return await Issue207_5().Match(
Succ: value => value,
Fail: excep => 0);
}
You can use the various conversion functions if you want to use it with `Either` in other places:
```c#
Issue207_5().ToEither(); // Task<Either<Exception, int>>
You can definitely think of Try<A> as a friendlier Either<Exception, A> and TryAsync<A> as a friendlier Task<Either<Exception, A>>.
There's also TryOption<A> which is basically: Either<Exception, Option<A>> and TryOptionAsync<A> which is Task<Either<Exception, Option<A>>>
Great, thanks for the explanation, I'll take a closer look at the Try family.
I accept this ;)
But if my Left case is not an exception type, I'm left with Matching the Either and throwing inside a Try. Which I suppose is not the end of the world, just a bit ugly IMO. Of course I could create an extension method to hide it away.
ex:
e.Match(
Right: Try,
Left: e => Try<T>(() => throw new Exception(...))
)
The other option I'm going to play with is Validation<L,R>. But if my chain is too long, it just doesn't flow right. And I don't want an Applicative.
I know I'm being overly pedantic here, but I like clean code ;)
I've created a gist with the solution that works for me. It also illustrates a real-world use-case for me.
Like I said above, wrapping Task<Either> to TryAsync feels dirty. I suppose it's just the nature of "bolting" FP onto a language that's not pure FP.
I hope this helps someone out. And if there's a nicer way of doing this, I'm all ears.
You know.... With the extension methods in place, this isn't so bad.
But if my Left case is not an exception type, I'm left with Matching the Either and throwing inside a Try. Which I suppose is not the end of the world, just a bit ugly IMO. Of course I could create an extension method to hide it away.
Yes, if your Left isn't an Exception then Try isn't appropriate.
The other option I'm going to play with is Validation
. _But if my chain is too long, it just doesn't flow right. And I don't want an Applicative_.
_(Emphasis mine)_ Not sure what you mean here?
Like I said above, wrapping Task
to TryAsync feels dirty. _I suppose it's just the nature of "bolting" FP onto a language that's not pure FP._
_(Emphasis mine)_ Not really. FP is fully supported in C# - it has first-class functions, lambdas etc. There isn't any sense that anything is being bolted on. What it didn't have (until I started this project) is a comprehensive and consistent 'base class library' for functional programming. Unfortunately even though this library covers the majority of what is needed, sometimes there are gaps. So what's missing here is EitherAsync, like OptionAsync, TryAsync. Because C# has built in language support for async it also needs bespoke implementations to be really useful in monadic programming. It is in my road-map, I am currently super busy though and probably won't get to it for a few weeks at least.
What you could do instead of writing those extension methods is to provide bespoke Select, SelectMany, and Where extensions for the Task<Either<..., ...>> type. If your Left is always a type called Error for example then I think the C# extension method resolver will prefer your version over a more generic one:
```c#
public static class Exts
{
public static async Task
(await self).Match(
Right: r => Right
Left: l => Left
public static async Task<Either<Error, C>> SelectMany<A, B, C>(
this Task<Either<Error, A>> self,
Func<A, Task<Either<Error, B>>> bind,
Func<A, B, C> project) =>
await (await self).MatchAsync(
Right: async a => (await bind(a)).Match(
Right: b => Right<Error, C>(project(a, b)),
Left: l => Left<Error, C>(l)),
Left: l => Left<Error, C>(l));
public static async Task<Either<Error, A>> Where<A>(this Task<Either<Error, A>> self, Func<A, bool> f) =>
(await self).Match(
Right: r => f(r)
? r
: Either<Error, A>.Bottom,
Left: l => l);
}
```
I have just typed that into the issue and not tested it, so apologies if it doesn't compile/work.
You're the best! 馃槏
@trbngr @chadedrupt
This was bugging me a bit, so I looked into a more permanent solution. If you get the latest from nu-get (v2.0.88) then the behaviour you expected will work.
Part of the problem was that the nested monadic bind called the outer monad's Fail function (on its class instance type). The Fail method is a bit of a hacky solution (it is in Haskell too), but it's needed to make the type-class stuff work with C#'s type system. The outer monad in this case is Task and its Fail function was BottomException.Default.AsFailedTask(), which is why the operation fails at that point.
Obviously you were expecting the failed inner value to propagate so the result would be Task(Left(x)). The Task monad now checks the failure value and if it matches its bound value type then it casts it to a valid result (I have done the same for the other monads too). This means the failed inner value is maintained within a successful outer value but still does the early out.
Here's a unit test to demo it:
```c#
public void Issue208()
{
var r = from a in Left
from b in Right
select a + b;
Assert.True(r.Result == Left<Error, int>(Error.New("error 1")));
var r2 = from a in Option<int>.None.AsTask()
from b in Some(1).AsTask()
select a + b;
Assert.True(r2.Result == None);
}
```
Thank you!!!!
Running 2.0.88 against my gist
Core.Tests.ExternalOptionsAndEithersTests.what_i_desire
Microsoft.CSharp.RuntimeBinder.RuntimeBinderInternalCompilerException : An unexpected exception occurred while binding a dynamic operation
at Microsoft.CSharp.RuntimeBinder.RuntimeBinder.Bind(DynamicMetaObjectBinder payload, IEnumerable`1 parameters, DynamicMetaObject[] args, DynamicMetaObject& deferredBinding)
at Microsoft.CSharp.RuntimeBinder.BinderHelper.Bind(DynamicMetaObjectBinder action, RuntimeBinder binder, IEnumerable`1 args, IEnumerable`1 arginfos, DynamicMetaObject onBindingError)
at Microsoft.CSharp.RuntimeBinder.CSharpConvertBinder.FallbackConvert(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
at System.Dynamic.DynamicMetaObject.BindConvert(ConvertBinder binder)
at System.Dynamic.ConvertBinder.Bind(DynamicMetaObject target, DynamicMetaObject[] args)
at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at CallSite.Target(Closure , CallSite , Object )
at LanguageExt.ClassInstances.MTask`1.Fail(Object err)
at LanguageExt.ClassInstances.MEither`2.<>c__1`3.<Bind>b__1_1(L l)
at LanguageExt.Either`2.Match[Ret](Func`2 Right, Func`2 Left, Func`1 Bottom)
at LanguageExt.ClassInstances.MEither`2.Bind[MONADB,MB,B](Either`2 ma, Func`2 f)
at LanguageExt.Trans`5.<>c__DisplayClass4_0`5.<Bind>b__0(InnerType inner)
at LanguageExt.ClassInstances.MTask`1.<>c__DisplayClass3_0`3.<Bind>b__1(Task`1 task)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at TaskExtensions.<SelectMany>d__5`3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at TaskExtensions.<SelectMany>d__5`3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Core.Tests.ExternalOptionsAndEithersTests.<what_i_desire>d__0.MoveNext() in /Users/chris/code/linktargeting/dotnet/test/Core.Tests/AsyncEitherTests.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Ok, leave it with me I'll put in some more tests.
@chadedrupt @trbngr Unfortunately I had to revert the changes. It turns out I can't generalise it as I would have hoped. There may be a few other approaches but I will need some time to investigate. So you will need to provide those Select and SelectMany overrides for now.
Here's the extension methods for anyone looking. Everything worked but SelectMany was named Select.
// ReSharper disable ConvertClosureToMethodGroup
public static async Task<Either<TError, B>> Select<TError, A, B>(this Task<Either<TError, A>> self, Func<A, B> f)
where TError : NewType<TError, string> =>
(await self).Match(
Right: r => Right<TError, B>(f(r)),
Left: l => Left<TError, B>(l));
public static async Task<Either<TError, C>> SelectMany<TError, A, B, C>(
this Task<Either<TError, A>> self,
Func<A, Task<Either<TError, B>>> bind,
Func<A, B, C> project
) where TError : NewType<TError, string> =>
await (await self).MatchAsync(
Right: async a => (await bind(a)).Match(
Right: b => Right<TError, C>(project(a, b)),
Left: l => Left<TError, C>(l)),
Left: l => Left<TError, C>(l));
public static async Task<Either<TError, A>> Where<TError, A>(this Task<Either<TError, A>> self, Func<A, bool> f)
where TError : NewType<TError, string> =>
(await self).Match(
Right: r => f(r)
? r
: Either<TError, A>.Bottom,
Left: l => l);
// ReSharper restore ConvertClosureToMethodGroup
Cheers!
This issue is fixed in v2.2.17-beta