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