带有多个 from 子句的 Either 的 language-ext 任务

language-ext Task of Either with multiple from clauses

我正在学习 FP with language-ext,我 运行 遇到了一个我无法克服的问题。我将我的代码简化为这个例子:

using System;
using System.Threading.Tasks;
using LanguageExt;
using static LanguageExt.Prelude;
using Xunit;

namespace Temp {

    public class SelectManyError {

        [Fact]
        public async Task Do() {

            var six = await from a in Task.FromResult(Right<Exception, int>(1))
                            from b in Task.FromResult(Right<Exception, int>(2))
                            from c in Task.FromResult(Right<Exception, int>(3))
                            select a + b + c;

        }
    }
}

我收到这个错误:

Multiple implementations of the query pattern were found for source type Task<Either<Exception, int>>. Ambiguous call to 'SelectMany'.

我通过阅读 this webpage 了解到编译器认为问题出在哪里。但是,我显然遗漏了或不理解一些重要的事情,因为我无法弄清楚这种情况是如何导致这个错误的,也不知道该怎么办。如果它只有 2 个来自子句,这将工作得很好,这让我更加困惑。

这是解决此类问题的错误方法吗?还有其他我不知道的方法吗?

编译器很难理解 a 的类型应该是什么(intEither<Exception, int>),因为它在第二个 [=14] 上未被使用=]行。

对于这个特定案例,这是一个非常丑陋的解决方法。但是,对于任何类型,我认为 hack 都可以适用于该类型。

using System;
using System.Threading.Tasks;
using LanguageExt;
using Xunit;
using static LanguageExt.Prelude;

public class Namespace
{
    [Fact]
    public async Task Method()
    {
        var six = await from a in Right<Exception, int>(1).AsTask()
                        from b in Right<Exception, int>(a - a + 2).AsTask()
                        from c in Right<Exception, int>(3).AsTask()
                        select a + b + c;
    }
}

这里是 Lang-ext 作者。我们一直在 lang-ext github repo 上讨论这个问题。

这些是我的评论:

很难。老实说,它们并不是真正的误报,因为 Either<L, R> 支持 + 运算符,因此属于 Task<R>SelectMany 将产生有效结果,就像SelectManyTask<Either<L, R>> 一起使用。

基本上 abc 值可以是 intEither<Exception, int>,具体取决于哪个 SelectMany 实现编译器选择。

整个表达式对所有 SelectMany 扩展都有效,这显然是我们有歧义的原因。

遗憾的是,将 var three = ... 更改为 Either<Exception, int> three = ... 并没有改变推理系统。因为这是编译器混淆的两个可能表达式之间的关键区别。

您可能想做的一件事是使用 OptionAsync<A> 而不是 Task<Either<L, R>> 而不是使用 Task<Option<A>>,而是使用 EitherAsync<L, R>。它们本质上是完全相同的类型,除了它很好地包装了所有绑定语义,所以您再也不会遇到这个问题。

我正在经历为 lang-ext 中的所有 monadic 类型创建 *Async 变体的过程。为方便起见,潜在的性能优势,并允许相当于 3 层嵌套的 monad:M<A<B<C>>> 例如 Seq<OptionAsync<A>>Seq<Task<Option<A>>>.

相同

无论如何,回到你上面的例子,你可以改为:

public async Task<int> Method()
{
    var six = from a in Right<Exception, int>(1).ToAsync()
              from b in Right<Exception, int>(2).ToAsync()
              from c in Right<Exception, int>(3).ToAsync()
              select a + b + c;

    return await six.IfLeft(0);
}

或者如果你想从 Task<int> 构造:

public async Task<int> Method()
{
    var six = from a in RightAsync<Exception, int>(Task.FromResult(1))
              from b in RightAsync<Exception, int>(Task.FromResult(2))
              from c in RightAsync<Exception, int>(Task.FromResult(3))
              select a + b + c;

     return await six.IfLeft(0);
}

或者,您可以留在 monad 中并且 return 那:

public EitherAsync<Exception, int> Method() =>
    from a in RightAsync<Exception, int>(Task.FromResult(1))
    from b in RightAsync<Exception, int>(Task.FromResult(2))
    from c in RightAsync<Exception, int>(Task.FromResult(3))
    select a + b + c;