1

I have a method like this...

public static async Task<Either<string, Card>> GetCard(/* parameters */) =>
  // code ommitted for clarity

...which I use like this (all parameters omitted for clarity)...

public static async Task<string> ActivateCard() =>
    await (
        from _ in CheckIssuerHash().ToAsync()
        from card in GetCard().ToAsync()
        from exp in CheckCardDetails().ToAsync()
        from displayNumber in SaveActivation().ToAsync()
        select displayNumber
      )
      .Match(JsonOk, JsonError);

The two methods JasonOk and JsonError are just convenience methods that convert a string into JSON. That all works fine, mainly bcause each of the methods in the Linq query return a Task<Either<string, T>> (where T varies between the methods).

I want to use GetCard in a query with a different generic type...

public static async Task<TransactionRequestStates> ProcessTransaction() =>
    await (
        from _ in CheckTransactionHash().ToAsync()
        from card in GetCard().ToAsync()
        // other methods get called here...
        select TransactionRequestStates.Success
      );

In this case, all other methods in the query return a Task<Either<TransactionRequestStates, T>>, where TransactionRequestStates is an enum. Unlike the ActivateCard method, which is called by a 3rd party, and so needs to return JSON, this method will be used internally, and so can return an enum value.

As it is, I get a compiler error "Cannot implicitly convert type 'LanguageExt.EitherAsync<string, Card>' to 'LanguageExt.Guard".

I think my problem is that I need to work out how to convert the Task<Either<string, T>> that GetCard returns to the Task<Either<TransactionRequestStates, string>> that all the other methods return.

In a nutshell, I want to know if there is way to convert a Task<Either<T1, TRes>> to a Task<Either<T2, TRes>>. That sounds like a standard thing to do in functional programming.

I have a simple mapping from the strings returned by GetCard to the appropriate TransactionRequestStates values, so that's not an issue. I just can't work out where to use this mapping.

I can't help feeling that this is so obvious that I should be able to do it with a single method from LanguageExt, but I'm not clear enough on it to know what.

Anyone able to advise? Please let me know if you need any more code or explanation. Thanks

Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106
  • Why do you use `.Match(JsonOk, JsonError)` in the second snippet? Shouldn't it be something like `.Match(ReturnsTransactionRequestStatesOk, ReturnsTransactionRequestStatesError)` ? – Guru Stron Jul 26 '22 at 23:45
  • Perhaps others can help, but I'd need a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Mark Seemann Jul 27 '22 at 05:41
  • Since you're asking a lot about these things, however, let me share a troubleshooting tip. LanguageExt is mainly a port of ideas from Haskell. Even in Haskell, I often find myself playing 'type Tetris'. In C#, getting the types to line up is sometimes even harder than in Haskell, since many of the ideas are foreign to C#. Try this: Instead of immediately returning an expression, assign it to a temp variable, like `var foo`. Then throw a `NotImplementedException` to make your code compile. Now use your editor (VS) to inspect the inferred type of `foo`. – Mark Seemann Jul 27 '22 at 05:45
  • @GuruStron You're absolutely right! I was mixing up the code where I was using `GetCard` before (where I need to return JSON, as the request comes form a 3rd party) with this current code, where I only need to return the `TransactionRequestStates` value. Thanks for pointing that out. – Avrohom Yisroel Jul 27 '22 at 13:43
  • @MarkSeemann I've made some major edits to the question, as sleeping on it clarified it a bit. The main point of the question is in the "In a nutshell" paragraph. I suspect that this paragraph on its own sums up exactly what I'm trying to do, the rest is just explaining my scenario. If you are able to answer that specific point, I should be able to work it out from there. An MRE would be quite tricky, but if that's needed, I'll try and pull one together. Thanks – Avrohom Yisroel Jul 27 '22 at 13:59
  • @MarkSeemann Thanks for the tip about type tetris. As it happens, I do do that sort of thing regularly, but my problem here isn't working out what the types are, it's working out how to convert what `GetCard` returns to be the same type as what the other methods return. Hopefully my updated question should make that clearer. – Avrohom Yisroel Jul 27 '22 at 14:00
  • [Either is a bifunctor](https://blog.ploeh.dk/2019/01/07/either-bifunctor), so you can use [BiMap](https://louthy.github.io/language-ext/LanguageExt.Core/Monads/Alternative%20Value%20Monads/Either/Either/index.html#Either_2_BiMap_2) to map both dimensions, or [MapLeft](https://louthy.github.io/language-ext/LanguageExt.Core/Monads/Alternative%20Value%20Monads/Either/Either/index.html#Either_2_MapLeft_1) to map just the left dimension. – Mark Seemann Jul 28 '22 at 07:54
  • The same is true for `EitherAsync`, which also defines async versions: [BiMapAsync](https://louthy.github.io/language-ext/LanguageExt.Core/Monads/Alternative%20Value%20Monads/Either/EitherAsync/index.html#EitherAsync_2_BiMapAsync_2), [MapLeftAsync](https://louthy.github.io/language-ext/LanguageExt.Core/Monads/Alternative%20Value%20Monads/Either/EitherAsync/index.html#EitherAsync_2_MapLeftAsync_1), etc. – Mark Seemann Jul 28 '22 at 07:58
  • @MarkSeemann Thanks for that. I'm working my way through your blog series on monoids, functors, etc, and am about 5 articles short of reaching bifunctors! – Avrohom Yisroel Jul 28 '22 at 17:15

1 Answers1

1

In a nutshell, I want to know if there is way to convert a Task<Either<T1, TRes>> to a Task<Either<T2, TRes>>. That sounds like a standard thing to do in functional programming.

You can call either.MapLeft(l => ...) to map the left-type of an Either. So, for Task<Either<T1, TRes>> to a Task<Either<T2, TRes>>, you can call:

tmx.Bind(mx => mx.MapLeft(l => ...))

Btw, I'm not sure why you've made the call into a Task<Either<L, R>>, none of your code is asynchronous. So, you can remove all of the async/Task stuff from your example.

louthster
  • 1,560
  • 9
  • 20
  • Thanks for that, was just what I needed. The reason none of the shown code is async was to try and simplify it for the question. In my real code, most (but not all) of the methods being called in the Linq query are async, which is why the methods return `Task>` rather than just `Either<>`. – Avrohom Yisroel Jul 28 '22 at 17:17