A friend recently showed me a cool program in elixir to demonstrate message passing -- two threads count by passsing ascending numbers back and forth. A quick search turns up a handful of examples.
I decided to implement this in Perl 6. Here's what I came up with →
$ping and $pong are each a Channel.
The subroutines ping and pong receive
from their corresponding channels and send to the other one.
If you run this, you'll see the numbers from one to five.
ping 1 (thread #3) pong 2 (thread #4) ping 3 (thread #3) pong 4 (thread #4) ping 5 (thread #3)
my ($ping, $pong) = Channel.new xx 2;
sub ping {
while $ping.receive -> $n {
say "ping $n (thread #{$*THREAD.id})";
$pong.send: $n + 1;
}
}
sub pong {
while $pong.receive -> $n {
last if $n >= 5;
say "pong $n (thread #{$*THREAD.id})";
$ping.send: $n + 1;
}
}
$ping.send: 1;
await Promise.anyof(
(start ping),
(start pong)
);
Next I wanted to encapsulate some common behavior. It's a ping-pong
"player" that's sending and receiving -- so let's make a Player class.
I imagine "ping" and "pong" to be the sounds made by each of the
player's paddles, as they hit the ball, so I added a $.sound attribute.
And an $.opponent attribute to represent, well, each player's
opponent.
$.channel is another attribute which handles (i.e. method delegation)
the send and receive methods.
Running this gives us something similar to the last one:
ping 1 (thread #3) pong 2 (thread #4) ping 3 (thread #3) pong 4 (thread #4) ping 5 (thread #3)
class Player {
has $.sound;
has $.opponent is rw;
has $.channel handles <send receive> = Channel.new;
has $.promise;
method play {
$!promise =
start {
while self.receive -> $n {
last if $n == 6;
say "$.sound $n (thread #{$*THREAD.id})";
$.opponent.send: $n + 1;
}
}
}
}
my ($ping,$pong) = <ping pong>.map: { Player.new(:$^sound) }
($ping, $pong)>>.opponent = ($pong, $ping);
($ping, $pong)>>.play;
$ping.send: 1;
await $pong.promise;
ping 1 pong 2 ping 3 pong 4 ping 5 pong 6 ping 7 Miss by pong. Score: 1 to 0 ping 1 pong 2 ping 3 pong 4 ping 5 pong 6 ping 7 Miss by pong. Score: 2 to 0 ping 1 pong 2 ping 3 pong 4 ping 5 pong 6 ping 7 pong 8 ping 9 Miss by pong. Score: 3 to 0 ping 1 Miss by pong. Score: 4 to 0 ping 1 pong 2 ping 3 Miss by pong. Score: 5 to 0 --Now serving: pong-- Miss by pong. Score: 6 to 0 pong 1 ping 2 pong 3 ping 4 Miss by pong. Score: 7 to 0 pong 1 ping 2 Miss by pong. Score: 8 to 0 pong 1 ping 2 Miss by pong. Score: 9 to 0 pong 1 Miss by ping. Score: 9 to 1 --Now serving: ping-- Miss by ping. Score: 9 to 2 ping 1 Miss by pong. Score: 10 to 2 ping 1 Miss by pong. Score: 11 to 2 Winner: ping Final Score: 11 to 2Some comments and things to notice:
$.sound handles stringification.promise was Kept, the loop exited, so a player missed.
>> is the same as » -- this is like map.
@sounds would set up a circle of message passing.
class Player {
has $.sound handles 'Str';
has $.opponent is rw;
has $.channel handles <send receive> = Channel.new;
has $.promise;
method missed {
$.promise.status == Kept;
}
method play {
$!promise = start {
while self.receive -> $n {
last if (^4).pick==1;
print "{$.sound} $n ";
self.opponent.send: $n + 1;
}
}
}
}
my @sounds = <ping pong>;
my @players = @sounds.map: { Player.new(:$^sound) }
@players».opponent «=» @players.rotate;
my $serving = @players[0];
my %score = @players Z=> 0 xx *;
@players».play;
repeat {
$serving.send(1);
await Promise.anyof(@players».promise);
with @players.first({ .missed }) -> $missed {
print "Miss by $missed.";
%score{$missed.opponent}++;
$missed.play;
}
say " Score: " ~ %score{@sounds}.join(' to ');
if %score.values.sum %% 5 {
$serving .= opponent;
say "--Now serving: $serving--";
}
} until %score.values.any >= 11 and (abs [-] %score.values) >= 2;
say "\nWinner: " ~ %score.invert.Hash{ %score.values.max };
say "Final Score: " ~ %score{@sounds}.join(' to ');
Conclusion and Further thoughts