Sterling has too many projects Blogging about Raku programming, microcontrollers & electronics, 3D printing, and whatever else...

React Blocks

| 510 words | 3 minutes | raku advent-2019
Football practice

The react block in Raku is the primary means of re-synchronizing asynchronous coding activity. Using it, you can easily pull together promises, supplies, and channels to make a coherent whole of your program or a subsystem.

A react block itself can run any code you want plus one or more whenever blocks. The code in the block will run once and the block will exit either when a done subroutine is called or when all the objects associated with whenever blocks are finished (i.e., all promises are kept or broken, all supplies have quit or closed, and all channels have failed or closed).

Aside from the syntax, the key thing to note about a react block is that all code inside of the block will always run as if single-threaded (i.e., multiple threads might be employed, but never will any code within this block be run concurrently).

Let’s take a look at an example react block:

my $commands = Channel.new;
my $input = Supplier.new;
my $output = Supplier.new;
my $quit = Promise.new;
react {
    print '> ';

    start loop { $input.emit: $*IN.getc }

    whenever $input.Supply.lines.map({ .trim }) {
        when /^add \s+ (\d+) \s+ (\d+)$/ { $commands.send: ('add', +$0, +$1) }
        when /^sub \s+ (\d+) \s+ (\d+)$/ { $commands.send: ('sub', +$0, +$1) }
        when 'quit' | 'exit' { $quit.keep }
        default { $output.emit: 'syntax error' }
    }

    whenever $commands -> @command {
        multi doit('add', Int $a, Int $b) { $a + $b }
        multi doit('sub', Int $a, Int $b) { $a - $b }

        $output.emit: doit(|@command);
    }

    whenever $output.Supply { .say; print '> ' }

    whenever $quit {
        say 'Quitting.';
        done;
    }
}

This program provides a small interactive shell that can perform addition and subtraction. When run, you might use it as follows:

> add 4 5
9
> sub 10 7
3
> exit
Quitting.

As you can see the code operates with a couple Supply objects, a Channel, and a Promise. Each of them work with whenever in the expected way. All of the code within the run block runs as if in a single thread (though, there’s no particular guarantee that only a single thread is used, only that no code will run concurrently).

Running from a single task with no concurrency does, however, present a problem in this case. The $*IN file handle only performs blocking reads, even if you use the .Supply method to get the data asynchronously. Therefore, we must pull input in a task running in a background thread, which is why we put a start before the loop that reads character input. Without this concurrent task, we’d have to hit the Return key extra times to give the other whenever clauses a chance to run.

That said, we could move the work of the other whenever blocks into separate concurrent tasks and just pull each together in this react block and it would work just as well. The goal of the react block is to synchronize the asynchronous work in a straightforward syntax. I think it does the job pretty well.

Cheers.

The content of this site is licensed under Attribution 4.0 International (CC BY 4.0).