Cleaning up my Language-Ext – or how to avoid awaiting an awaitable

I’ve been trying to write functional code for some time now, and still have the feeling that I’m not doing it correctly, especially when seemingly simple things don’t work. Having bumped into one specific issue over and over again, I decided to try and sort it out once and for all.

Imagine a stupidly simple API endpoint that take a float, and returns the square root doubled. I have helper functions as follows…

static Either<string, double> Sqrt(double d) =>
  d >= 0.0
    ? Math.Sqrt(d)
    : "Error: Negative number";

static Either<string, double> Double(double d) =>
  d > 10.0
    ? "Error: Too big to double"
    : 2 * d;

My API endpoint returns a string of the form “Result: 23.3” if successful, or an error if not.

I wire these together as follows…

public static string Calc(double d) =>
  (from res1 in Sqrt(d)
   from res2 in Double(res1)
   select res2)
  .Match(res => $"Result: {res}",
    err => err);

This works as expected. Obviously, my real code would have more from clauses than this, but I’m trying to keep this simple.

Now, suppose (which is more realistic) the helper functions were async, then I would need to modify my endpoint to look like this…

public static async Task<string> Calc(double d) =>
  await (from res1 in Sqrt(d).ToAsync()
         from res2 in Double(res1).ToAsync()
         select res2)
    .Match(res => $"Result: {res}",
      err => err);

Again, all works.

However, now in the case of errors, I want to log them, send an email, etc, so the second lambda to the Match method needs to be async. In order to avoid a compiler error, I need to make the first async as well, and then `await` the result…

public static async Task<string> Calc(double d) =>
  await await (from res1 in Sqrt(d).ToAsync()
               from res2 in Double(res1).ToAsync()
               select res2)
    .Match(async res => $"Result: {res}",
      async err => {
        // Log the error, send an email, etc (async);
        return err;
      });

This starts to look like I’m just doing this completely wrongly. Maybe I’m wrong, but awaiting an awaited task doesn’t seem right.

It turns out that there is a MatchAsync method designed to handle such a situation. This enables me to write the method like this…

public static Task<string> Calc(double d) =>
     (from res1 in Sqrt(d).ToAsync()
      from res2 in Double(res1).ToAsync()
      select res2)
     .MatchAsync(res => Task.FromResult($"Result: {res}"),
         async err => {
           await Task.Delay(1);
           // Log the error, send an email, etc (async);
           return err;
         }
     );

As we’re using MatchAsync, we don’t need to await the value returned from Match. We do need to use Task.FromResult() in the first lambda to avoid a compiler error, but I often find I need to do that anyway, so it’s not a major problem.

Thanks to GuruStrom for suggesting this, along with the fact that as MatchAsync returns a Task, as does the method, we don’t need the method to be async, and can drop the first await, leaving much cleaner code.

Be First to Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.