简体   繁体   中英

How do I bind an async method that returns an Either to an async method that accepts an Option in Language-Ext?

This is a follow-up question to a similar one I asked about binding monads that return different types. I realised after getting a clear answer that I hadn't asked the full question. Rather than amend that question (which does stand on its own so is worth leaving), Mark Seemann (who answered) suggested I ask this as a new question, so here goes.

For simplicity, I'm presenting a use-case that isn't really realistic (eg role-checking could be done differently, etc), but I'm trying not to confuse the question, so please bear with me.

Suppose I want to write a method that accepts an int , and needs to...

  1. Check the authed used is in the appropriate role to make the request
  2. Check the Id corresponds to a customer in the database
  3. Check if the customer is active

If we get through all that lot, we return the customer, if not we return an error message. All methods are async .

I have the following (simplified) methods...

async static Task<Either<string, int>> CheckUser(int id) {
  // Check the authed user is in the right role, etc. For simplicity, we'll just branch on the id
  // Simulate some async work
  await Task.Delay(0);
  if (id < 0) {
    return "Invalid";
  }
  return id;
}

async static Task<Option<Customer>> Exists(int id) {
  // Check the customer id refers to a real customer. Simulate some async work
  await Task.Delay(0);
  return id < 10 ? None : new Customer(id, "Jim Spriggs");
}

async static Task<Either<string, Customer>> IsActive(Customer c) {
  // Simulate some async work
  await Task.Delay(0);
  if (c.Id % 2 == 0) {
    return "Inactive";
  }
  return c;
}

record Customer(int Id, string Name);

I would like to bind these together as follows (in reality I would be doing more than writing the results to the console, but you get the idea)...

await CheckUser(31)
  .Bind(async id => (await Exists(id)).ToEither("No such customer"))
  .Bind(IsActive)
  .Match(n => Console.WriteLine($"Success: {n}"), ex => Console.WriteLine($"Ex: {ex}"));

However, I get a compiler error on the id parameter to Exists on the 2nd line... " CS1503 Argument 1: cannot convert from 'LanguageExt.Either<string, int>' to 'int' "

I tried it with and without the await/async keywords, but still couldn't get it to compile. I'm not sure if I need to add them in the lambdas or not.

Anyone able to explain how I do this? Thanks

You're running into problems because not only is Either a monad , asynchronous computations (Tasks) are too . Thus, the Bind method you're trying to call is associated with Task<T> rather than Either<L, R> .

You can also see that the inferred type of id in the Bind method is Either<string, int> rather than int .

The most convenient way to deal with problems like this is to treat the 'stack' of monads ( Task<Either<L, R>> ) as a 'composed' monad. LanguageExt comes with such a type out of the box: EitherAsync .

You can transform a Task<Either<L, R>> to an EitherAsyn<L, R> value with the ToAsync method:

[Theory]
[InlineData(-1, "Ex: Invalid")]
[InlineData( 9, "Ex: Unknown customer")]
[InlineData(36, "Ex: Inactive")]
public async Task Answer(int id, string expected)
{
    var actual = await CheckUser(id).ToAsync()
        .Bind(i => Exists(i).ToEitherAsync("Unknown customer"))
        .Bind(c => IsActive(c).ToAsync())
        .Match(n => $"Success: {n}", ex => $"Ex: {ex}");
    Assert.Equal(expected, actual);
}

All the above test cases pass.

Notice that you have to convert each of the Task<Either<L, R>> values to EitherAsync<L, R> with ToAsync() and the Task<Option<Customer>> value to EitherAsync<string, Customer> with ToEitherAsync . This is a bit of ceremony you have to go through to keep the methods ( CheckUser , Exists , IsActive ) 'clean'.

Alternatively, you could change the methods to return EitherAsync values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM