2

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.

If I use an Either-returning method for step 2, I can do something like this...

static Either<string, int> CheckUser(int n) {
  // Check the authed user is in the right role, etc
  // For simplicity, we'll decide based on the Id
  if (n < 0) {
    return "Not authorised to access customer data";
  }
  return n;
}

static Either<string, Customer> Exists(int n) =>
  // This would check the database
  n < 10 ? "Unknown customer" : new Customer(n, "Jim Spriggs");

static Either<string, Customer> IsActive(Customer c) {
  // This would check the customer, we just use a simple check on the Id for simplicity
  if (c.Id % 2 == 0) {
    return "Inactive";
  }
  return c;
}

record Customer(int Id, string Name);

I can then bind this together as follows...

CheckUser(36)
  .Bind(Exists)
  .Bind(IsActive)
  .Match(n => Console.WriteLine($"Success: {n}"), ex => Console.WriteLine($"Ex: {ex}"));

This works, but I can't help but feel that the Exists method should return an Option<Customer> rather than an Either<string, Customer>, eg (again simplified for clarity)...

static Option<Customer> Exists(int n) =>
    n < 10 ? Option<Customer>.None : new Customer(n, "Jim Spriggs");

However, I'm struggling to work out how to bind this between the other two methods. I thought that I could use Map to convert, but couldn't work out how to do this.

Anyone able to advise? Is it OK to use Either, or should I be using Option? If the latter, how do I fix my code?

Thanks

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106

1 Answers1

1

Use ToEither:

[Fact]
public void Answer()
{
    var actual = CheckUser(36)
        .Bind(i => Exists(i).ToEither("Inactive"))
        .Bind(IsActive)
        .Match(n => $"Success: {n}", ex => $"Ex: {ex}");
    Assert.Equal("Ex: Inactive", actual);
}

The above test passes.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Hmm, just realised that in my real code, all these helper methods are `async`, and if I replace the return types with `Task<...>` then I get a compiler error on the 2nd line of your code, where it calls `ToEither`. I changed that line to `.Bind(async id => (await Exists(id)).ToEither("No such customer"))` but now get a compiler error on the `id`parameter... _cannot convert from 'LanguageExt.Either' to 'int'_. Are you able to advise how I would fix this? Thanks again – Avrohom Yisroel Jul 05 '22 at 16:34
  • @AvrohomYisroel It sounds like you need stacks of monads, and possibly a traversal. Feel free to post a new question. – Mark Seemann Jul 05 '22 at 17:53
  • Thanks for the suggestion. I [opened a new question](https://stackoverflow.com/questions/72874733/how-do-i-bind-an-async-method-that-returns-an-either-to-an-async-method-that-acc), so if you have chance to look at it I'd be very grateful. – Avrohom Yisroel Jul 05 '22 at 19:44