Home > Enterprise >  Compiler error trying to work out what to put in an Either.Match lambda
Compiler error trying to work out what to put in an Either.Match lambda

Time:01-10

I have a situation in which I have a bunch of methods that all require the same code at the beginning. In order to reduce duplication, I want to move that code into a general helper method that would take a lambda parameter for the case-specific code.

The scenario involves loyalty cards, which are issued by card issuers. All methods take a card issuer Id and some JSON (serialised as a string as it came in from an API). The JSON is deserialised to an object (to make it easier to work with). If all went well, the method returns a success message (a string), and if there was a problem, returns the error (also a string).

One example of a method is to check if a card is active. The JSON contains the card number.

I'm using Language-Ext, and have static utility methods that return Either<string, T> with <T> being specific to the method.

My intention was to have an overall method that (as well as the issuer Id and the serialised JSON) take a delegate that does the case-specific code. My current attempt looks like this...

static async Task<string> Execute<T>(string issuerId, 
                                     string json, 
                                     Func<RequestDto<T>, Task<Either<string, string>>> specific) =>
  // First call the helper method, see below
  (await GetDto<T>(issuerId, json))
    .Match(async requestDto =>
      // We got the incoming data OK, so call the case-specific method
      (await specific(requestDto))
      .Match(response => {
        // Everything went fine, return the response
        return response;
      }, msg => {
        // There was a problem (invalid issuer Id, etc), return the error message
        return msg;
      }),
     // There was a problem getting the DTO, return the error message
     x => x);

This relies on the following records and helper methods...

record CardIssuer(string Id, string EncryptionKey);
record RequestDto<T>(CardIssuer CardIssuer, T Dto);

static async Task<Either<string, RequestDto<T>>> GetDto<T>(string issuerId, string json) =>
  // First get the card issuer
  await from cardIssuer in GetCardIssuer(issuerId).ToAsync()
        // Deserialise the JSON to a case-specific object
        let dto = JsonSerializer.Deserialize<T>(json)
        // Wrap the two into something the specific method can accept
        select new RequestDto<T>(cardIssuer, dto);

static Either<string, CardIssuer> GetCardIssuer(string id) =>
  // Would get the card issuer from the database, or return Left if not found
  id == "1" ? new CardIssuer("1", "abc") : new CardIssuer(id, "def");

An example of a case-specific method would be to check if a card is valid...

record CheckCardRequest(string cardSerialNumber);

static async Task<Either<string, string>> CheckCard(RequestDto<CheckCardRequest> dto) =>
  // Would check the card against the database and return something more realistic
  dto.Dto.cardSerialNumber == "11"
    ? Right<string, string>("Ok")
    : Left<string, string>("Bleah");

This provides a complete example of what I want to do, although it is obviously very simplified. The real code is all async as it involves database calls, and uses Either as there are many places where things can go wrong. I simplified all of that to provide a complete example.

My problem is with the lambda x => x at the very end of the Execute method.

I get a red underline on the second x with a compiler error "Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement."

As a general rule, I understand why this error can be raised, but I don't understand why I get it here. I thought that the lambda x => x was returning a value, so was an expression, not a statement.

I've tried changing it to x => return x and other variations, but just get different compiler errors.

Anyone able to explain what I need to do to get this working?

P.S. This is related to a question I asked earlier, but as it's quite different, I thought I should open a new question for this one. If I can get this working, then it would provide a good solution for what I asked there.

CodePudding user response:

It seems that due to type mismatch (async lambda async requestDto =>... returns Task<string>'s so the second parameter of Match should also have the same return type) the Unit Match(Action<R> Right, Action<L> Left, Action Bottom = null) overload is selected. After fixing the issue (by changing x => x to x => Task.FromResult(x)) you will need to await the match result. And then you can simplify/refactor a bit with the final method looking like this:

static async Task<string> Execute<T>(string issuerId,
    string json,
    Func<RequestDto<T>, Task<Either<string, string>>> specific) =>
    await (await GetDto<T>(issuerId, json))
        .Match(
            async requestDto => (await specific(requestDto)).Match(s => s, s =>s),
            Task.FromResult);
  • Related