React Blocks
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.