Match on Either<,> Needs to Return Type Over ValueTuple from Async Lambda
CarlLanglais opened this issue · comments
We are having issues with correct async usage and correct type returning from and async call within a Match on Either<,> that was returned. This isn't an issue with the LaYumba package and more of an issue of we don't know the fix and would like to try to get more FP experienced eyes on it to help solve it.
https://stackoverflow.com/questions/70655009/match-on-either-needs-to-return-type-over-valuetuple-from-async-lambda
Sorry if this is not where this should be posted, but only initial place I could think of.
There are 2 overloads to Match
. The first one takes two functions (handling the left and right value, respectively) both of which should return a result of type TR
, so this is also the type returned by Match
.
The second overload takes two actions, and as a result it returns Unit
, i.e. ValueTuple
, the empty tuple.
When you make one of your functions async
, then your left function has type L -> TR
, while your right function has type R -> Task<TR>
, so the types don't line up for the first overload, and I guess the compiler is going to the 2nd overload (also because your second lambda is not returning a value. This is why you're seeing ValueTuple
.
This is a case in which you are dealing with 2 effects (asynchrony and validation) so this is discussed in a dedicated chapter in the book. (Actually it's half a chapter in the 1st edition, split out into a dedicated chapter in the 2nd edition).
What do you want to end up with? is it an Either<L, Task<TR>>
(in which case use Map
), or a Task<TR>
(in which case your left function also needs to return a value of this type), or is it a Task<Either<L, TR>>
which you can then await, and match on (in which case use Traverse
to apply the async operation, then await
, then Match
).
Of course when nesting Task
and Either
things get complex, so to simplify it's better if you only use one monad. One approach is taking the advantage of the fact that Task
can fail, so in the Left
case create a failed Task
(probably using FromException
). Another better approach would be to create your own structure that is awaitable
and also contains a case for failed validation, that is different from Exceptions.
I can't look into the exact details of your scenario, but I'm sure that with these ideas you'll be able to find a solution.
Thanks for the quick response. The reason for my troubles makes sense now. I'll re-read the chapters and try the suggestions.
Just wanted to put the change I got to from the suggestions and rereading before closing this. Thanks for the help.
var validateResult = ValidateEventMessage(message) // Validation<LotusNotesStatusMessage>
.Traverse(UpdateUnidAsyncTest); // Traverse to go from Validation<Task<Exceptional<bool> to Task<Validation<Exceptional<bool>
var validated = await validateResult; // Validation<Exceptional<bool>
var eventTracer = validated
.Match(
Invalid: (invalid) => errTrace(ValidationFailMessage(invalid.ToString())),
Valid: (valid) => valid
.Match(
Exception: (exception) => errTrace(exception.Message),
Success: (success) => successTrace(SuccessUnidUpdateMessage(success))
)
);