【问题标题】:languageExt Either and type inferencinglanguageExt 和类型推断
【发布时间】:2017-08-28 20:55:42
【问题描述】:

(与my previous question相关)

我正在使用 C# 中的语言扩展库并试图让 Tasks 返回 Either 值以正确组合并且在类型推断方面遇到困难。它只是没有按照我的预期进行。

这里涉及到三种方法:

  • Initialization:返回Task<Either<Exception, ADUser>>
  • createUserMapping:接受ADUser 并返回Either<Exception, UserMapping>
  • AddUser:接受 UserMapping 并返回 Task<Either<Exception, int>>

我想返回AddUser 的结果。看起来很简单。不过,不知何故,这些类型并没有排成一行。

我现在拥有的是这样的:

return Initialization
    .Bind(eu => eu.Bind(createUserMapping).AsTask())
    .Bind(eu => eu.Bind(async u => await AddUser(u)));

但是,虽然 Intellisense 告诉我最后一个异步 lambda 中的 uUserMapping 类型,但当它被传递到 AddUser 调用时,我得到了错误:

Argument 1: cannot convert from 'System.Exception' to 'UserMapping'

为什么编译器在 lambda 开始时对参数的类型没有混淆?有没有更好的方法让它发挥作用?

我尝试过使用BindT 以及LINQ 表达式的变体。我似乎无法解决这个问题。

【问题讨论】:

  • 这里使用的Bind 方法的签名是什么? 4个方法一样吗?两个lambas中eu的类型是什么?我认为您不想在最后一个羔羊等待,而只是返回Task
  • @JamesFaix,问题出在我使用的库版本中。我试图避免使用 beta 版本,但如果我想让它工作,我必须迁移到 2.0。类型是正确的,但库只是在 1.9 版中没有合作。在 2.0+ 时确实如此。

标签: c# functional-programming async-await


【解决方案1】:

好吧,我不知道为什么 Bind 会这样,但我确实找到了一个可行的解决方案。我现在没有直接调用AddUser,而是有一个新的本地方法addUser,它接受Either 并处理请求。它看起来像这样:

    private async Task<Either<Exception, int>> addUser(Either<Exception, UserMapping> eu)
    {
        var nestedTask = eu.Map(u => uow.ControllerRepository.AddUser(u));

        var rv = nestedTask.Match(
            Right: t => identity(t),
            Left: e => Left<Exception, int>(e).AsTask());
        return await rv;
    }

这里,nestedTask 的类型是 Either&lt;Exception, Task&lt;Either&lt;Exception, int&gt;&gt;&gt;。所以我在Match 中把它拆开,只返回Right 值或Left(异常),以Task&lt;Either&lt;Exception, int&gt;&gt; 的形式包装。

我还将createUserMapping 更改为Either,而不是UserMapping

现在我的顶级转换序列如下所示:

    public async Task<Either<Exception, int>> AddUser(ADUser user)
    {
        return await Initialization
            .Bind(eadu => createUserMapping(eadu).AsTask())  // eadu: Either<Exception, ADUser>
            .Bind(eum => addUser(eum));                      // eum:  Either<Exception, UserMapping>
    }

我不知道为什么我最初的尝试没有奏效,但至少我现在有一个可行的解决方案。

如果有人能阐明我所面临的问题,我仍然想听听。

=== 编辑 4/5/17 ===

好的。问题是我使用的是旧版本的库(pre 2.0)。当我将库升级到最新的测试版(我希望避免测试版)时,我不再遇到问题了。

【讨论】:

    【解决方案2】:

    我想我会从language-ext issue you raised 发布解决方案,以防万一其他人被难住了。

    public Task<Either<Exception, int>> Issue207() =>
        Initialization
            .BindT(createUserMapping)
            .BindT(addUser);
    
    static Task<Either<Exception, ADUser>> Initialization =>
        Right<Exception, ADUser>(ADUser.New("test user")).AsTask();
    
    static Either<Exception, UserMapping> createUserMapping(ADUser user) =>
        Right<Exception, UserMapping>(UserMapping.New(user.ToString() + " mapped"));
    
    static Task<Either<Exception, int>> addUser(UserMapping user) =>
        Right<Exception, int>(user.ToString().Length).AsTask();
    

    问题在于使用不匹配的一元类型。版本1.x 对转换器类型的容忍度较低,尤其是在提供给Bind 的委托中返回错误的转换器类型。 2.x 版本现在支持返回 BindT 的内部 monad 或外部 monad(而不仅仅是 1.x 上的内部 monad),这使代码看起来更漂亮。

    v1v2 上,Bind 函数只接受Either&lt;L, R&gt;(对于Either monad)和Task&lt;A&gt; 对于Task monad 的返回值(对于提供的绑定委托)。这是 Bind 的正确签名。在 v1 和 v2 中,还有大量代码生成的“转换器”扩展方法,允许您使用嵌套的单子 M1&lt;M2&lt;A&gt;&gt;(如 Task&lt;Either&lt;L, R&gt;&gt;)。这些转换器函数都有一个T 后缀(如BindT)。在版本 1 中,BindT 还具有代表返回 Either&lt;L, R&gt;Task&lt;A&gt;(转换器的内部单子)的签名,而在 v2 中,每种转换器类型都有两个 BindT 扩展,一个返回内部单子(如v1)和一个允许返回外部单子的单子(即Task&lt;Either&lt;L, R&gt;&gt;)。所以我使用BindT 的解决方案只能使用v2

    你仍然可以在v1 中实现你想要的,只是它没有v2 解决方案那么有吸引力。您的原始解决方案的问题是,这个eu.Bind(async u =&gt; await AddUser(u)) 正在对euEither 执行Bind,但eu 上的Bind 的返回类型应该是Either&lt;L, R&gt;。而你正在喂它Task&lt;Either&lt;L, R&gt;&gt;。所以这就是你看到你所做的错误的原因。

    如上所述,v2 中的 BindT 接受嵌套 monad 的结果类型,这就是为什么返回 Task&lt;Either&lt;L, R&gt;&gt;BindT 一起工作的原因。

    使用函数式编程“遵循类型”总是很重要的(我发现)。它们是比 OO 世界更强大的真理。因此,如果事情不合适,那么它们要么需要映射到正确的类型,要么你有一个错误。

    【讨论】:

    • 我不确定您所说的“返回错误的变压器类型”是什么意思。我在 2.x 中遇到过类似的问题,但它们更容易找到解决方案。如果某处不匹配,我可能会在这里有一个更根本的误解。我仍然不明白出了什么问题。
    • 在 v1 上,Bind 函数只接受Either&lt;L, R&gt;(对于Either monad)和Task&lt;A&gt; 的返回值(对于提供的绑定委托)Task monad。这是Bind 的正确签名。在 v1 中还有大量代码生成的“transformer”扩展方法,允许您使用嵌套的单子 M1&lt;M2&lt;A&gt;&gt;(如 Task&lt;Either&lt;L, R&gt;&gt;)。这些转换器函数都有一个T 后缀(如BindT)。 BindT 也有签名让代表返回 Either&lt;L, R&gt;Task&lt;A&gt;(变压器的内部单子)...
    • ... 而在 v2 中,每种转换器类型都有两个 BindT 扩展,一个返回内部 monad(如 v1 中),一个允许返回外部 monad(即Task&lt;Either&lt;L, R&gt;&gt;)。所以我使用BindT 的解决方案只能使用v2。
    • 你仍然可以在 v1 中实现你想要的,只是它没有 v2 解决方案那么有吸引力。您原来的解决方案的问题是,这个eu.Bind(async u =&gt; await AddUser(u)) 正在对euEither 执行Bind,但eu 上的Bind 的返回类型应该是Either&lt;L, R&gt;。而你正在喂它Task&lt;Either&lt;L, R&gt;&gt;。所以这就是你看到你所做的错误的原因。如上所述,v2 中的 BindT 接受嵌套 monad 的结果类型,这就是为什么返回 Task&lt;Either&lt;L, R&gt;&gt; 'just works' with BindT
    • 在函数式编程中“遵循类型”总是很重要的(我发现)。它们是比 OO 世界更强大的真理。因此,如果事情不合适,那么它们要么需要映射到正确的类型,要么你有一个错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多