(与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表达式的变体。我似乎无法解决这个问题。
答案 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),这使得代码看起来更漂亮。
在v1
和v2
上,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
也有代表签名返回BindT
或Either<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))
Bind
上Either
,eu
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版之前)。当我将库升级到最新版本时(我希望避免测试版)我不再有问题。