5

I am using the Language-Ext library for C# and I am trying to chain asynchronous operations that return an Either type. Let's say that I have three functions that would return an integer if they succeed and a string if the fail, and another function that sums the result of the previous three functions. In the sample implementations below Op3 fails and returns a string.

public static async Task<Either<string, int>> Op1()
{
    return await Task.FromResult(1);
}

public static async Task<Either<string, int>> Op2()
{
    return await Task.FromResult(2);
}

public static async Task<Either<string, int>> Op3()
{
    return await Task.FromResult("error");
}

public static async Task<Either<string, int>> Calculate(int x, int y, int z)
{
    return await Task.FromResult(x + y + z);
}

I want to chain these operations and I am trying to do it like this:

var res = await (from x in Op1()
                 from y in Op2()
                 from z in Op3()
                 from w in Calculate(x, y, z)
                 select w);

But we the code does not compile because I get the error cannot convert from 'LanguageExt.Either<string, int>' to 'int' for the arguments of Calculate. How should I chain these functions?

Carlos Rodriguez
  • 523
  • 1
  • 5
  • 13
  • Your Op2 and Op3 method accept arguments, but none of them is provided with any ? Is this a copy paste error or the actual problem of your code. Locally i've tested your code and it builds successfully after i've supplied Op2 and Op3 with arguments. – vasil oreshenski Feb 19 '18 at 16:40
  • That was a typo, indeed. The problem arises when `Op2` and `Op3` do not have the arguments. – Carlos Rodriguez Feb 19 '18 at 19:30

2 Answers2

5

The problem is that the LINQ query can't work out which version of SelectMany to use, because x isn't used in the second line. You can get around this by converting your Task<Either<L, R>> to EitherAsync<L, R>:

    public static async Task<int> M()
    {
        var res = from x in Op1().ToAsync()
                  from y in Op2().ToAsync()
                  from z in Op3().ToAsync()
                  from w in Calculate(x, y, z).ToAsync()
                  select w;

        return await res.IfLeft(0);
    }

Or, instead of returning Task<Either<L, R>> return EitherAsync<L, R>:

    public static EitherAsync<string, int> Op1() =>
        1;

    public static EitherAsync<string, int> Op2() =>
        2;

    public static EitherAsync<string, int> Op3() =>
        3;

    public static EitherAsync<string, int> Calculate(int x, int y, int z) =>
        x + y + z;

    public static async Task<int> M()
    {
        var res = from x in Op1()
                  from y in Op2()
                  from z in Op3()
                  from w in Calculate(x, y, z)
                  select w;

        return await res.IfLeft(0);
    }
louthster
  • 1,560
  • 9
  • 20
0

x, y and z are not of type int, but of Either<string, int> change Calculate(int x, int y, int z) to accept instances of Either calls: Calculate(Either<string, int> x, Either<string, int> y, Either<string, int>z), or pass

x.{The int getter property}
Radin Gospodinov
  • 2,313
  • 13
  • 14
  • I think that's not the problem. If I turn all the functions synchronous (removing all the `Task`, `async`, `await`), then the type is correctly inferred and if I run it the computation is aborted when `Op3` returns the string (as expected). All the values are correctly unwrapped from the monad in this case. I suspect that the problem has something to do with the fact that `Either` is wrapped in a `Task`, but I am not getting it to work. – Carlos Rodriguez Feb 19 '18 at 13:23
  • @Radin is correct. The `from .. in` syntax only unwraps one monadic type. That is why the synchronous version of your code works...because you are manually removing the `Task<>` monad and the `from..in` syntax unwraps the `Either` to `int`. I see two solutions. You can nest calls to `MapT` (which unwraps two monadic types at once) or you can upgrade to the current prerelease version and replace the nested monad types `Task>` with the single monad type `EitherAsync<,>`. – Tyson Williams Feb 20 '18 at 02:14
  • @TysonWilliams I think I see how it could work using `MapT`, but I do not see it working with `EitherAsync<,>`. If I replace `async Task` in the signature of my functions with `EitherAsync<,>` then I cannot `await` tasks inside the methods. I can return a task, but I cannot `await`. Am I missing something? (PS: I have tried the pre-release version of the library). – Carlos Rodriguez Feb 20 '18 at 14:40
  • Don't change how you are using `async` and `await`. – Tyson Williams Feb 20 '18 at 14:46
  • 2
    If I keep `async` and `await` then the compiler warns me that `The return type of an async method must be void, Task, Task, ValueTask or another tasklike type`. I am using the version 2.2.11-beta. – Carlos Rodriguez Feb 20 '18 at 14:57
  • Oh, I misread your previous comment. Yes, you should not use `async` and `await` in your `Op` methods. I thought you were referring to your use of `async` and `await` in the top-level method. – Tyson Williams Feb 27 '18 at 14:07