📘 Transfer non-scalar objects through Perl 6 channels

Channels may also transfer both arrays and hashes and do it as easily as they work with scalars. Unlike Perl 5, an array will not be unfolded to a list of scalars but will be passed as a single unit. Thus, you may write the following code.

my $c = Channel.new;
my @a = (2, 4, 6, 8);
$c.send(@a); 
say $c.receive; # [2 4 6 8]

The @a array is sent to the channel as a whole and later is consumed as a whole with a single receive call.

What’s more, if you save the received value into a scalar variable, that variable will contain an array.

my $x = $c.receive;
say $x.WHAT; # (Array)

The same discussions apply to hashes.

my $c = Channel.new;
my %h = (alpha => 1, beta => 2);
$c.send(%h); 

say $c.receive; # {alpha => 1, beta => 2}

Instead of calling the list method, you can use the channel in the list context (but do not forget to close it first).

$c.close;
my @v = @$c;
say @v; # [{alpha => 1, beta => 2}]

Note that if you send a list, you will receive it as a list element of the @v array.

Here is another example of “dereferencing” a channel:

$c.close;
for @$c -> $x {
    say $x;
} # {alpha => 1, beta => 2}

📘 Channels in Perl 6

Perl 6 includes a number of solutions for parallel and concurrent calculations. The great thing is that this is already built-in into the language and no external libraries are required.

The idea of the channels is simple. You create a channel through which you can read and write. It is a kind of a pipe that can also easily transfer Perl 6 objects. If you are familiar with channels in, for example, Go, you would find Perl 6’s channels easily to understand.

In this section, we discuss the simplest applications of channels, where things happen in the same thread. The big thing about channels is that they transparently do the right thing if you’re sending in one or more threads, and receiving in another one or more threads. No value will be received by more than one thread, and no value shall be lost because of race conditions when sending them from more than one thread.

📘 Basics of promises in Perl 6

The Promise.new constructor builds a new promise. The status of it can be read using the status method. Before any other actions are done with the promise, its status remains to be Planned.

my $p = Promise.new;
say $p.status; # Planned

When the promise is kept, call the keep method to update the status to the value of Kept.

my $p = Promise.new;
$p.keep;
say $p.status; # Kept

Alternatively, to break the promise, call the break method and set the status of the promise to Broken.

my $p = Promise.new;
say $p.status; # Planned 

$p.break;
say $p.status; # Broken

Instead of asking for a status, the whole promise object can be converted to a Boolean value. There is the Bool method for that; alternatively, the unary operator ? can be used instead.

say $p.Bool;
say ?$p;

Keep in mind that as a Boolean value can only take one of the two possible states, the result of the Boolean typecast is not a full replacement for the status method.

There is another method for getting a result called result. It returns truth if the promise has been kept.

my $p = Promise.new;
$p.keep;
say $p.result; # True

Be careful. If the promise is not kept at the moment the result is called, the programme will be blocked until the promise is not in the Planned status anymore.

In the case of the broken promise, the call of result throws an exception.

my $p = Promise.new;
$p.break;
say $p.result;

Run this programme and get the exception details in the console.

Tried to get the result of a broken Promise

To avoid quitting the programme under an exception, surround the code with the try block (but be ready to lose the result of say—it will not appear on the screen).

my $p = Promise.new;
$p.break;
try {
    say $p.result;
}

The cause method, when called instead of the result, will explain the details for the broken promise. The method cannot be called on the kept promise:

Can only call cause on a broken promise (status: Kept)

Like with exceptions, both kept and broken promises can be attributed to a message or an object. In this case, the result will return that message instead of a bare True or False.

This is how a message is passed for the kept promise:

my $p = Promise.new;
$p.keep('All done');
say $p.status; # Kept
say $p.result; # All done

This is how it works with the broken promise:

my $p = Promise.new;
$p.break('Timeout');
say $p.status; # Broken
say $p.cause;  # Timeout

📘 The start keyword in Perl 6 promises

The start method creates a promise containing a block of code. There is an alternative way to create a promise by calling Promise.start via the start keyword.

my $p = start {
    42
}

(Note that in Perl 6, a semicolon is assumed after a closing brace at the end of a line.)

The start method returns a promise. It will be broken if the code block throws an exception. If there are no exceptions, the promise will be kept.

my $p = start {
    42
}

say $p.result; # 42
say $p.status; # Kept

Please note that the start instruction itself just creates a promise and the code from the code block will be executed on its own. The start method immediately returns, and the code block runs in parallel. A test of the promise status will depend on whether the code has been executed or not. Again, remember that result will block the execution until the promise is not in the Planned status anymore.

In the given example, the result method returns the value calculated in the code block. After that, the status call will print Kept.

If you change the last two lines in the example, the result may be different. To make the test more robust, add a delay within the code block.

my $p = start {
    sleep 1;
    42
}

say $p.status; # Planned
say $p.result; # 42
say $p.status; # Kept

Now, it can be clearly seen that the first call of $p.status is happening immediately after the promise has been created and informs us that the promise is Planned. Later, after the result unblocked the programme flow in about a second, the second call of $p.status prints Kept, which means that the execution of the code block is completed and no exceptions were thrown.

Would the code block generate an exception, the promise becomes broken.

my $p = start {
    die;
}
try {
    say $p.result;
}

say $p.status; # This line will be executed
               # and will print 'Broken'

The second thing you have to know when working with start is to understand what exactly causes an exception. For example, an attempt to divide by zero will only throw an exception when you try using the result of that division. The division itself is harmless. In Perl 6, this behaviour is called soft failure. Before the result is actually used, Perl 6 assumes that the result is of the Rat (rational) type.

# $p1 is Kept
my $p1 = start {
    my $inf = 1 / 0;
}

# $p2 is Broken
my $p2 = start {
    my $inf = 1 / 0;
    say $inf;
}

sleep 1; # Wait to make sure the code blocks are done

say $p1.status; # Kept
say $p2.status; # Broken

📘 The then method in Perl 6 promises

The then method, when called on an already existing promise, creates another promise, whose code will be called after the “parent” promise is either kept or broken.

my $p = Promise.in(2);
my $t = $p.then({say "OK"}); # Prints this in two seconds

say "promised"; # Prints immediately
sleep 3;

say "done";

The code above produces the following output:

promised
OK
done

In another example, the promise is broken.

Promise.start({  # A new promise
    say 1 / 0    # generates an exception
                 # (the result of the division is used in say).
}).then({        # The code executed after the broken line.
    say "oops"
}).result        # This is required so that we wait until
                 # the result is known.

The only output here is the following:

oops

📘 The anyof and allof methods in Perl 6 promises

Another pair of factory methods, Promise.anyof and Promise.allof, creates new promises, which will be only kept when at least one of the promises (in the case of anyof) is kept or, in the case of allof, all of the promises listed at the moment of creation are kept.

One of the useful examples found in the documentation is a timeout keeper to prevent long calculations from hanging the programme.

Create the promise $timeout, which must be kept after a few seconds, and the code block, which will be running for longer time. Then, list them both in the constructor of Promise.anyof.

my $code = start {
    sleep 5
}
my $timeout = Promise.in(3); 

my $done = Promise.anyof($code, $timeout);
say $done.result;

The code should be terminated after three seconds. At this moment, the $timeout promise is kept, and that makes the $done promise be kept, too.

📘 An example of using promises: Sleep sort in Perl 6

Finally, a funny example of how promises can be used for implementing the sleep sort algorithm. In sleep sort, every integer number, consumed from the input, creates a delay proportional to its value. As the sleep is over, the number is printed out.

Promises are exactly the things that will execute the code and tell the result after they are done. Here, a list of promises is created, and then the programme waits until all of them are done (this time, we do it using the await keyword).

my @promises;
for @*ARGS -> $a {
    @promises.push(start {
        sleep $a;
        say $a;
    })
}

await(|@promises);

Provide the programme with a list of integers:

$ perl6 sleep-sort.pl 3 7 4 9 1 6 2 5

For each value, a separate promise will be created with a respective delay in seconds. You may experiment and make smaller delays such as sleep $a / 10 instead. The presence of await ensures that the programme is not finished until all the promises are kept.

As an exercise, let’s simplify the code and get rid of an explicit array that collects the promises.

await do for @*ARGS {
    start {
        sleep $_;
        say $_;
    }
}

First, we use the $_ variable here and thus don’t have to declare $a. Second, notice the do for combination, which returns the result of each loop iteration. The following code will help you to understand how that works:

my @a = do for 1..5 {$_ * 2};
say @a; # [2 4 6 8 10]