2019/08/15

async/await in Perl 5 and Python 3

Python 3 allows programmers to write programs in an asynchronous style, in order to achieve higher performance through concurrency. Object types like Future and language features like the async/await syntax mean that asynchronous functions can be written in a natural way that reads similar to straight-line code. The function can be suspended midway through execution until a result arrives that allows it to continue.

Here is an example which shows how you can use these to implement a function that suspends in the middle until it has received a response to the HTTP request we sent. We request a JSON-encoded list of numbers, and return their sum:

  import aiohttp

  async def get_sum_from_url(url):
    async with aiohttp.ClientSession() as session:
      async with session.get(url) as response:
        data = await response.json()
        return sum(data.numbers)

We can write a similar thing in Perl 5. In Python 3 the event system is built into the language, whereas in Perl 5 we get to choose our own. Because of this, the example is a little more verbose because it has to specify more of these choices - creating the IO loop and adding the HTTP client to it.

  use Future::AsyncAwait;

  use IO::Async::Loop;
  use Net::Async::HTTP;

  use JSON::MaybeXS 'decode_json';
  use List::Util 'sum';

  my $loop = IO::Async::Loop->new;

  my $http = Net::Async::HTTP->new;
  $loop->add( $http );

  async sub get_sum_from_url($url)
  {
    my $response = await $http->GET( $url );
    my $data = decode_json( $response->content );

    return sum( $data->{numbers}->@* );
  }

The examples in both languages make use of a type of object that wraps up the idea of "an operation that may still be pending" - which both languages call a Future. While minor differences exist between the two languages - such as the methods on them - the overall idea remains the same. Essentially, the value is a placeholder for a result that will come later.

In both languages we see the await keyword, which operates on an expression. The argument to the await keyword is a value of one of these future objects. The await keyword is used to suspend the currently-running function until that result is available. Once the result arrives, the await expression itself yields that deferred result.

Similarly, in both languages the async keyword decorates a function declaration and remarks that it may return its own result asynchronously via one of these futures, and allows that function to make use of the await expression.

This similarity is no coïncidence. The Future::AsyncAwait module which adds the async/await syntax to Perl 5 was designed specifically to look and feel very similar to this feature in several other languages - of which Python 3 is one.

This async/await syntax makes the code read similarly to how it would look if we were not using futures to make it asynchronous, but instead just using the return values of functions directly. This similarity of notation is the reason why we prefer to use the await syntax if we can, as it helps readability of the code. Compare this syntax with earlier techniques - such as callback functions - where the structure of the code can often look very different.

By providing the same (or at least similar) semantics behind the same kind of notation, each language retains a sense of familiarity to users of other languages. It allows readers to make more sense of the program at first glance because the same sorts of structures with the same sorts of behaviour exist there too. By sharing these ideas, each ecosystem gains the strengths of those ideas it borrows from the other, to the overall benefit of both.

No comments:

Post a comment