Home > Software design >  How do I bind a Task<List<Card>> to Either<> with Language-Ext
How do I bind a Task<List<Card>> to Either<> with Language-Ext

Time:08-05

I have devices (eg mobile phones, payment terminals, etc) that can request a list of cards held in a database. For security, the devices need to authorise themselves, and present their serial number in the JWT token. If any of this doesn't match up, we reject the request.

Thanks to lots of recent help with related questions, I now have two methods that I use in similar scenarios...

static Either<int, string> GetDeviceSerialNumberFromToken() {
  // Get the JWT token from the request header, and extract the device serial number.
  // If the token is invalid or serial number isn't present, we return
  // an error code (int). If the data is all OK, we return the serial
  // number. For simplicity, the latter is used here...
  return "abc123";
}

static async Task<Either<int, Device>> GetDevice(string serialNumber) {
  // Check if we have a device with that serial number, and some other checks
  // As this involves the database, this method is async. Here we simulate that...
  await Task.Delay(10);
  return new Device();
}

These are very simplified, omitting the parameters, etc, but that shouldn't affect the question.

In the current scenario, I don't actually need the device object, I just need to know that GetDevice returned it, rather than the error code.

Assuming we got past these two methods, I then want to get the list of cards and return it. I have a simple enough method for getting the list...

static async Task<List<Card>> GetCards() {
  // As this involves the database, this method is async. Here we simulate that...
  await Task.Delay(10);
  return new List<Card>();
}

My intention was to tie this all together in the same way I've done in my other scenarios...

var cardList = await(
        from deviceSerialNumber in GetDeviceSerialNumberFromToken().ToAsync()
        from _ in GetDevice(deviceSerialNumber).ToAsync()
        from cards in GetCards()
        select cards
);

As this would be an Either, I would call Match on this, and return an error code if either of the first two methods failed, but that part isn't relevant for this question.

My problem is that this gives a compiler error on the line that calls GetCards() "Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List>' to 'LanguageExt.Guard'."

Following the answer I was given in a previous question, I tried adding ToAsync() as follows...

from cards in GetCards().ToAsync()

...but this gave a compiler error "'Task<List>' does not contain a definition for 'ToAsync' and the best extension method overload 'TaskTryExtensions.ToAsync(Try)' requires a receiver of type 'Try'."

I can't await the call to GetCards(), as you can only await the first line in such a query. I tried doing it the wrong way round, ie getting the data before the checks...

var cardList = await(
        from cards in await GetCards()
        from deviceSerialNumber in GetDeviceSerialNumberFromToken().ToAsync()
        from _ in GetDevice(deviceSerialNumber).ToAsync()
        select cards
);

...but that gave a compiler error of "Could not find an implementation of the query pattern for source type 'List'. 'SelectMany' not found."

At this point I'm stuck. As usual, I imagine there is a very simple answer, but I can't see it. Anyone able to advise?

Thanks

CodePudding user response:

When you use the

from x in foo.ToAsync()
from y in bar.ToAsync()

syntax, all values must be in the same monad. Here you start with GetDeviceSerialNumberFromToken().ToAsync(), which is an EitherAsync value, so all subsequent values must also be EitherAsync values.

This would be more apparent to you if you try to produce a minimal, reproducible example. When I did this with the code posted in the OP, I didn't get the compiler error reported, but rather this one:

An expression of type 'Task<List<Card>>' is not allowed in a subsequent from clause in a query expression with source type 'EitherAsync<int, <anonymous type: string deviceSerialNumber, Device _>>'. Type inference failed in the call to 'SelectMany'

I think it should be fairly clear from this message that you need to find a way to 'lift' Task<List<Card>> to EitherAsync<int, List<Card>>.

One option is to use RightAsync:

var cardList = await (
    from deviceSerialNumber in GetDeviceSerialNumberFromToken().ToAsync()
    from _ in GetDevice(deviceSerialNumber).ToAsync()
    from cards in EitherAsync<int, List<Card>>.RightAsync(GetCards())
    select cards);

With the little I know of LanguageExt, there's probably a more convenient helper method than that, but that's the gist of it.


Indeed, things get a little easier if one imports LanguageExt.Prelude:

var cardList = await (
    from deviceSerialNumber in GetDeviceSerialNumberFromToken().ToAsync()
    from _ in GetDevice(deviceSerialNumber).ToAsync()
    from cards in RightAsync<int, List<Card>>(GetCards())
    select cards);
  • Related