Either and type inferencing issue
See original GitHub issueI’m using the language-ext library in C# and trying to get Tasks returning Either values to compose properly and having a tough time with the type inferencing. It just isn’t working out the way I would expect.
There are three methods involved here:
Initialization
: Returns aTask<Either<Exception, ADUser>>
createUserMapping
: Takes anADUser
and returns anEither<Exception, UserMapping>
AddUser
: takes anUserMapping
and returns aTask<Either<Exception, int>>
I would like to return the results of AddUser
. It seems like a pretty simple application of Bind operations. Somehow, though, the types are just not lining up.
What I have now is this:
return Initialization
.Bind(ead => ead.Bind(createUserMapping).AsTask())
.Bind(eum => eum.Bind(async u => await AddUser(u)));
In the first operation, the outer Bind
takes the Either
and calls the (nested) Bind
which takes the Right
value (an ADUser
) and returns an Either<Exception, UserMapping>
which is wrapped up in a Task
(as a result of the call to AsTask()
).
Likewise, in the second case the outer Bind
takes the Either
and calls the (again, nested) Bind
which takes the Right
value (a UserMapping
) and returns a Task<Either<Exception, int>>
.
That is the theory, anyway.
However, while Intellisense is telling me that the u
in the last async lambda is of type UserMapping
(at the point of declaration), when it gets passed into the AddUser
call I get the error:
Argument 1: cannot convert from 'System.Exception' to 'UserMapping'
Why is the compiler getting confused as to the type of the parameter when it isn’t confused about it at the start of the lambda? Is there a better way of doing this?
One thing that does work, but seems a bit of a hack to me, is to not call AddUser
directly. Instead I have a new local method, addUser
: of type Either<Exception, UserMapping> -> Task<Either<Exception, int>>
. It is implemented like this:
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;
}
Here, nestedTask
is of type Either<Exception, Task<Either<Exception, int>>>
. So I take that apart in the Match
, returning just the Right
value directly or the Left
(Exception) wrapped appropriately - as a Task<Either<Exception, int>>
.
I also changed createUserMapping
to take the Either
instead of taking a UserMapping
.
Now my top-level sequence of transformations looks like:
return await Initialization
.Bind(eadu => createUserMapping(eadu).AsTask()) // eadu: Either<Exception, ADUser>
.Bind(eum => addUser(eum)); // eum: Either<Exception, UserMapping>
This makes my top-level code simpler and works as I expect it to but still leaves me confused as to why the original code just wouldn’t work.
I know that C#'s type inferencing is limited compared to other languages but this has me baffled. Why would the u
parameter in the lambda be recognized correctly at the point of declaration but the point that it is used find it switched to another type?
Issue Analytics
- State:
- Created 6 years ago
- Comments:17 (9 by maintainers)
Top GitHub Comments
Thanks. I was getting errors on the second BindT so I abandoned that approach. 😃 It works now, thanks.
@trbngr No problem. You can definitely think of
Try<A>
as a friendlierEither<Exception, A>
andTryAsync<A>
as a friendlierTask<Either<Exception, A>>
.There’s also
TryOption<A>
which is basically:Either<Exception, Option<A>>
andTryOptionAsync<A>
which isTask<Either<Exception, Option<A>>>