languageExt任一和类型推理

时间:2017-04-03 20:48:25

标签: c# functional-programming async-await

(与my previous question相关)

我正在使用C#中的language-ext库并试图让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告诉我最后一个async lambda中的u属于UserMapping类型,但当它传递到AddUser调用时我得到错误:

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

为什么编译器在lambda开头没有混淆时会对参数的类型感到困惑?是否有更好的方法让它发挥作用?

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

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();

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

v1v2上,Bind函数只接受Either<L, R>(对于Either monad)和{{1}的返回值(对于提供的绑定委托) } Task<A> monad。这是Bind的正确签名。在v1和v2中,还有一堆代码生成的“变换器”扩展方法,允许您使用嵌套的monad Task(如M1<M2<A>>)。这些变换器函数都具有Task<Either<L, R>>后缀(如T)。在版本1上BindT也有代表签名返回BindTEither<L, R>(变换器的内部monad),而在Task<A>中有两个v2每个转换器类型的扩展,一个返回内部monad(如BindT),另一个允许返回外部monad(即v1)。因此,我使用Task<Either<L, R>>的解决方案只适用于BindT

你仍然可以在v2中达到你想要的效果,它不会像v1解决方案那样具有吸引力。原始解决方案的问题在于,此v2正在eu.Bind(async u => await AddUser(u)) BindEithereu Bind的返回类型应该是eu。而你正在喂它Either<L, R>。所以这就是你看到你所犯错误的原因。

如上所述,Task<Either<L, R>>中的BindT接受嵌套monad的结果类型,这就是为什么返回v2'只适用于Task<Either<L, R>>的原因。

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

答案 1 :(得分:0)

好吧,我不知道为什么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<Exception, Task<Either<Exception, int>>>。所以我在Match中将其区分开来,只返回Right值或Left(例外)包裹的Task<Either<Exception, int>>

我还更改了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 ===

行。问题是我使用的是旧版本的库(2.0版之前)。当我将库升级到最新版本时(我希望避免测试版)我不再有问题。