2020/12/22

2020 Perl Advent Calendar - Day 22

<< First | < Prev | Next >

We started off this advent calendar series looking at the async/await syntax provided by Future::AsyncAwait, and the way that functions can be marked as async. More recently we have been looking at the class and object syntax provided by Object::Pad, such as syntax to provide named methods. Some of you may be wondering whether these two things can be combined; whether methods can be marked as being asynchronous. The answer is yes.

The way that these two modules are implemented means that they can coƶperate on how functions are parsed. The end result is that a method can be declared using the combined keywords async method and it behaves exactly as expected. Namely, that $self and the class's slot variables are available within the code, it returns a future-wrapped value, and permits the await keyword.

For example, back on day 6 we saw an example of await with a //= shortcircuit expression to optionally wait for a read operation to fill a cache on an object, implemented with a $self->{...} key inside async sub. At the time I said that the example was slightly reworded from the original code. That is because in reality, the code is implemented using the combination of async and method:

use Object::Pad;
use Future::AsyncAwait;

class Device::Chip::TSL256x extends Device::Chip;

...

has $_TIMINGbytes;

async method _cached_read_TIMING ()
{
    return $_TIMINGbytes //= await $self->_read(REG_TIMING, 1);
}

In fact, almost every post after that also had some code taken from modules that are implemented using async method. In each case, the real code was in fact shorter and more concise than the posted example because it did not have to start with the my $self = shift; line initially, and could use the shorter slot variables instead of hash key accesses on $self->{...}.

These two syntax modules - either individually or in combination - are able to greatly neaten a lot of common code patterns. To see just how much they provide here is what the method above might have been written if neither syntax module was used:

sub _cached_read_TIMING
{
    my $self = shift;

    return Future->done($self->{TIMINGbytes})
        if defined $self->{TIMINGbytes};
    
    return $self->_read(REG_TIMING, 1)->then(sub {
        ($self->{TIMINGbytes}) = @_;
        return Future->done($self->{TININGbytes});
    });
}

In this version of the code it is far less obvious to see the flow of the logic. The caching behaviour of the TIMINGbytes field is harder to see, hidden by the various machinery of the future return value and ->then chaining. Additionally, the $self->{TIMINGbytes} field is referred to four times here - each one being just a hash key, and thus prone to typoes. Sure there are techniques to help detect such problems with classical Perl hash-based objects (such as locked hashes), but those all detect runtime attempts to actually touch the fields; none of them are able to point out problems at compiletime.

Such an error would be detected at compiletime using an Object::Pad-based slot variable:

has $_TIMINGbytes;

async method _cached_read_TIMING {
    return $_TININGbytes //= await $self->_read(REG_TIMING, 1);
}
$ perl -c example.pl
Global symbol "$_TININGbytes" requires explicit package name
  (did you forget to declare "my $_TININGbytes"?) at ...

By the way, did anyone spot the typo on the long example code above? I didn't, the first time I wrote it... ;)

<< First | < Prev | Next >

No comments:

Post a Comment