Perl 6 Scripting Games
Are you interested in playing with Perl 6 and Rakudo Perl but can't figure out what to do? Here's an idea that came up during Jon Allen's talk "The Camel and the Snake" at YAPC::EU in Copenhagen.
For the past few years Microsoft has sponsored an annual Scripting Games competition, where they publish problems of varying difficulty levels to be solved using Perl 5, VBScript, Python, or other languages. I think it might be very interesting and useful to see people develop and publish Perl 6 solutions to those problems.
So, here's my idea: If you're interested in learning more about Perl 6, select one or more problems from the Scripting Games website, develop solution(s) for them in Perl 6, and then publish your solutions somewhere along with a description of what you like, don't like, learned, didn't learn, etc. about Perl 6 and Rakudo Perl.
One of the things we've observed from our experience with the November Wiki and other similar projects is that having "running code examples" and "real problems" is one of the best drivers for compiler and language development. I'm thinking that having people craft solutions to the scripting problems might do more of the same for Rakudo, while also sparking discussion and reflection on Perl 6 itself.
So, where to start? Start by obtaining and building a copy of Rakudo Perl, write and test your solutions to one or more problems, and post the results and your experiences somewhere for others to see. You can post to use.perl.org, your private blog, the perl6-users mailing list, or anywhere else you find convenient. The point isn't to develop a centralized repository of solutions (although we can do that), but rather to use the problems as a way to spread discussion, feedback, and experience with Perl 6.
I should also make it plain that people are very likely to run into some of Rakudo Perl's "rough edges" -- places where we don't yet implement key features or where they don't work exactly as they're supposed to. But to the designers and implementors that's part of the point -- we need to know where those rough edges are. Overall, I'm hoping that with the recent improvements to Rakudo Perl there won't be so many rough edges as to make the effort more disappointing than enjoyable. And there are lots of people eager to answer questions and help out on the perl6-users mailing list, IRC #perl6 (irc.freenode.net), and other common Perl forums. It's all about learning and improving what we have with Perl 6.
I look forward to seeing your questions and answers.
Happy holidays,
Pm

Initial attempt at beginners event 1 (pairs of cards): http://use.perl.org/~dpuu/journal/38142
Fun with Alice. (The reverse characters in each word one.)
## First the obvious solution to anyone that's been reading rakudo.org
#"Evil is a deed as I live.".subst( /(\S+)/ , { $0.reverse; } , :g).say;
## --> Method 'reverse' not found for invocant of class 'PGE;Match'
# Hrm OK that should probably work, but easily fixed by quoting it, right?
#"Evil is a deed as I live.".subst( /(\S+)/ , { "$0".reverse; } , :g).say;
# --> too few arguments passed (1) - 2 params expected
# Well that's strange. Because this works:
"foo".reverse.say;
# --> oof
# ...but not apparrently on strings that have interpolated content.
# Anyway let's find a way to get around that shall we?
#"Evil is a deed as I live.".subst( /(\S+)/ , { $^a.reverse; } , :g).say;
# --> too few arguments passed (1) - 2 params expected
# nope.
# How about
"Evil is a deed as I live.".subst( /(\S+)/ , { reverse $0 } , :g).say;
# --> Evil is a deed as I live.
# OK that apparently did nothing
"Evil is a deed as I live.".subst( /(\S+)/ , { reverse $^a } , :g).say;
# --> livE si a deed sa I .evil
# That's the stuff. And of course the final solution should be sorta like:
#(=$*IN)>>.subst( /(\S+)/ , { reverse $^a } , :g).say;
# Statement not terminated properly at line 27, near ">>.subst(
# Ahh no >>. hyperoperator yet.
# OK so fine we'll do a for loop
# for =$*IN { $^d.subst( /(\S+)/ , { reverse $^a } , :g).say };
# And that works, minus ensuring output and input record separators are the
# same -- really we should suck the whole thing in and do multiline subst
# to preserve the record separators even in cases where they differ.
# But let's take a step back -- take a look at this wreckage discovered
# by the side of the road.
"Evil is a deed as I live.".subst( /(\S+)/ , { "!" ~ $^a } , :g).say;
# --> !Evil ! i !i !!i d !i ! !i !
# Yeah it looks like the match strings are being taken from offsets inside
# the mangled result, not from the original string. Oops.
# OK what if we decide we don't want to use subst...
# First let's check out that new comb function maybe that would be
# fun to use:
#comb(/\S+/, "these are words. all of them").perl.say;
# --> "Could not find non-existent sub comb"
# Oh well no fun for us today.
# Alright we still have a working split right?
split(/\s+/, "these are words. all of them ").perl.say;
# --> ["these", "are", "words.", "all", "of", "them", ""]
# Looking good, we can live with the less dwimmy null string at the end
# but we need to leave \s+ untouched so we'll be needing them.
split(/(\s+)/, "these are words. all of them ").perl.say;
# --> ["these", "are", "words.", "all", "of", "them", ""]
# Well that does not appear to work yet. And hey by the way the
# synopsis says: "Also unlike Perl 5, the string to be split is always
# the invocant or first argument." Hrm then why does the above work?
# Maybe my understanding of what constitutes an invocant is faulty,
# but that should be trying to split " " using the longer string as a
# separator.
#
# Maybe MMD is figuring that out because the first argument is a Rule?
split(" ", "these are words. all of them ").perl.say;
# --> ["these", "are", "words.", "all", "of", "them", ""]
# Nope. It's voodoo! well I have no time for black magic today.
# Other than to note the example of the use of comb using arguments
# in this order and a similar prototype, and then shrug. (Functions.pod)
#
# Let's use a more perl6ish syntax to sweep that issue under the rug.
"these are words. all of them ".split(/\s+/).perl.say;
# ["these", "are", "words.", "all", "of", "them", ""]
# Well that works as expected
# But still, now how to either get an array of shuffled whitespace and values,
# or two separate arrays which we can shuffle back together? Actually the
# latter might be preferable. But I was hoping to avoid using a regular
# expression based solution to show Rakudo can do things without resorting
# to regexps at every turn, as effective as they may be.
"these are words. all of them ".split(/>/).perl.say;
# --> ["these", " ", "are", " ", "words", ". ", "all", " ", "of", " ", "them", " "]
# OK not quite there -- they want all \S included in words
# token rule_backslash: { \S };
# --> Statement not terminated properly at line 96, near ": { \\S "
# No easy out for us.
# At this point I'm too tired to get into zero-width assertions. And besides
# we are falling back into relying on regexp for everything. Actually rather
# than abusing subtypes and mmd with constraints to do an unshift() lifo
# monstrosity I think sleep is a better option.
# Other side notes:
#("these are words. all of them " ~~ /(\s+)|(\S+)/).perl.say;
# --> Method 'perl' not found for invocant of class 'PGE;Match'
#("these are words. all of them " ~~ /(\s+)|(\S+)/).say;
# --> Method 'say' not found for invocant of class 'PGE;Match'
# Which raises an interesting question as to whether Match "literals"
# should have a compact representation both for output and for parse,
# rather than the normal Dump of an object... hmmm.... what would
# a human readable huffman representation of a Match literal look like.
# Anyhow... I'm sincerely encouraged at the pace of development, I guess
# would be the most politic thing to say :-) Pretty much I'll be blocked
# on doing anything I want to do with Rakudo until we have fully functional
# int8/int16/int32, :unsigned, etc plus bufs, +>> +& +| that doesn't drop
# high order bits, and everything else needed to parse, edit, and
# apply methods to raw packet data in bounce buffers without copying
# it out. Oh yeah and concurrency. I know I'm a demanding S.O.B. but
# us low level interface dwellers are less impressed with string tricks
# and more into packed data -- for which I have been mulling a new
# Parrot... erm I forget the acronym. It begins with P. :-)
# But maybe I'll take a hack at a few more of these "problems" over the
# holidays just for fun.
Yeah and readers will have to guess where the HTML markup ate parts of my code. Sigh.
The Bowling Challenge: http://use.perl.org/~dpuu/journal/38145. For this one I found multi-subs very useful!
This is exactly why working on "real problems" is so useful. Here's what we have now in Rakudo (r34324), based on the issues skid1 uncovered:
$ ./parrot perl6.pbc > "Parrot speaks your language".subst( /(\S+)/, { $0.reverse }, :g).say torraP skaeps ruoy egaugnal > "Parrot speaks your language".subst( /(\S+)/, { "$0".reverse }, :g).say torraP skaeps ruoy egaugnal > "Parrot speaks your language".subst( /(\S+)/, { $^a.reverse }, :g).say torraP skaeps ruoy egaugnal > "Parrot speaks your language".subst( /(\S+)/, { reverse $0 }, :g).say torraP skaeps ruoy egaugnal > "Parrot speaks your language".subst( /(\S+)/, { reverse $^a }, :g).say torraP skaeps ruoy egaugnal > "Parrot speaks your language".subst( /(\S+)/, { "!" ~ $^a }, :g).say !Parrot !speaks !your !language > ("these are words. all of them " ~~ /\s+|\S+/).say theseSome of the other issues pointed out remain primarily because the spec is incomplete (e.g., 'split' where the regex contains captures) or still under discussion. But this did solve a bunch of issues in Rakudo.
Thanks for some terrific test cases and analysis!
Pm
and the phone-numbers-to-words challenge: http://use.perl.org/~dpuu/journal/38158
plus a couple of one-lines
event 6 asks us to generate the prime numbers from 1 to 200:
% ./perl6 -e 'my @primes; for 2 .. 200 -> $n { @primes.push($n) unless $n % any(@primes)==0 }; .say for @primes'
and event 7 asks for a random scheduling of a round-robin tournament with 6 teams:
% ./perl6 -e 'my @teams = "A" .. "F"; my @games = (@teams X~X @teams).grep: { [le] $_.split("") } ; .say for @games.pick( @games.elems )'
Instant runoff voting:
# Accumulate per-candidate totals considering disqualified candidates
sub tally ( %votes, %disqual ) {
my @c;
my %t;
my %d2;
%d2 = %disqual;
# While writing this I noticed odd behavior for arity-1 blocks with .kv
# which should probably run the loop alternately on keys and values
# Fortunately we are arity-2 here.
for %votes.kv {
@c = split(",",$^k);
while %d2{@c[0]} { shift(@c); };
# Now I'd like to think we could use junctions and hypers to
# good effect here but did not try
%t{@c[0]} += $^v if @c.elems;
}
return %t.pairs.sort({$^a.value}); # why not .v?
}
# now why the %d2 in &tally? Well two reasons.
# First reason
my %foo;
say "yeah that won't happen" if %foo{"hello"};
%foo.perl.say;
# --> {("hello" => undef)}
# Don't remember autovivication of keys on read from reading the spec
# Second reason
sub ff ( %f is copy) {
%f{"hello again"} = 1;
}
my %bar;
ff(%bar);
%bar.perl.say;
# --> {("hello again" => 1)}
# As we do not consider hashes to be "references" anymore... ?
my %votes;
my %disqual;
for =$*IN { %votes{$^a}++ };
my $max = 0;
do {
my @tally;
my $total;
@tally = tally(%votes, %disqual);
@tally.perl.say;
# It would be real nice to use a hyper/reduction here -- lets see should
# .values grok Array of Pairs if and when we have Array of Pairs?
# Hrm well again as with last time there's no >>. yet.
# And doing it with map really isn't shorter -- so back to good old for
for @tally { $total += $^a.value };
# OK, now, last element of an array
# @results[-1].perl.say;
# --> undef
# Forget what the spec says about negative subscripts...
# pop(@results).perl.say;
# --> "No applicable methods."
# hrm...
(("Winner " ~ @tally.pop.key ~ "!").say and exit)
if @tally[@tally.elems - 1].value > $total/2;
# No winner, disqualify the lowest vote getters.
%disqual{@tally[0].key}++;
"disqualified: ".print;
%disqual.keys.say;
# Though really if there's a tie for last place there needs to be a
# special action taken. But the problem description said there would
# be no ties.
} while(True);
# Test input for a race between candidates #1, #2, #3 and #4
# (It demonstrates the need for special handling in disqualification ties)
#1,#2,#3,#4
#1,#3,#2,#4
#2,#3,#1,#4
#4,#3,#1,#2
#3,#1,#4,#2
In order to get slicing to work, Rakudo is currently autovivifying hashes and arrays on read. We'll correct this soon.
There are also known issues in Rakudo with passing any sort of aggregate to a subroutine -- they don't always map properly. That's high on my hit list of things to fix, unfortunately a lot of stuff has been built up around the incorrect implementation so it's going to take some dedicated hacking/refactoring time to correct.
Thanks for the additional solutions!
Pm
Blackjack: http://use.perl.org/~dpuu/journal/38160. I think junctions need a little more polishing from a usability perspective -- but they did the trick!
Probably the last one for today: my approach(es) to the Instant Runoff Election: http://use.perl.org/~dpuu/journal/38161
Me and some other contributors have been posting code for WSG at a perl6-examples git repository I started. Feel free to fork and add content, or download and run them with rakudo. If you want added as a collaborator (for WSG or any other perl6 examples) just jump on #perl6 and let me know your github username.