22 February 2016

Roll for Initiative, part 1

Oh hi! Just a short post for the moment because I am somewhat overwhelmed with things. But it can easily turn into some sort of multi-part thing. We're looking at more Perl 6 fun.

It all started when I was looking through Perl 6 docs, pondering what should be my second thing to learn about and write. I came across the metaoperators, which is a treasure trove of rather fantastic things that you can do in other languages just fine but tend to need tedious, repetitive looping constructs. Perl 6? Baked into the language, of course.

Smooth Operator

Obviously, there's lots of ways to sum a list of numbers. Possibly you loop over the list, or perhaps you have some mathematical functions you can import from your mathematical library. There's other things you can do to lists of things that give you a single scalar result, too. This kind of operation is generally termed a Reduce operation, in terms of the whole Filter-Map-Reduce class of algorithms.

In Perl 6, you can turn a regular operator like + into one that operates on every element in a list and reduces it to a single value. This is done by enclosing it in square brackets, like my $total = [+] @list. But it will work for more than just +:

$ perl6
> my $five-factorial = [*] 1..5;
120

Jumping to Hyperspace

Next on the list is "Hyper" operators. They are used to expand some operation to run on each element of lists, possibly on the left or right (or both) side of the expression, and return a list of all the results. It can get pretty complex if you're wanting to do something to two arrays of differing length, but I'll let you experiment with the wikibooks examples for that. For me, the killer feature has to be »., the hyper-method-invocation operator.

Suppose you have a Die class that has a .roll method. You have a big array of these dice, and you want to run .roll on all of them. You could do a traditional loop, or you could do a foreach-style Perl 6 for @dice -> $die { $die.roll } sort of thing. But a far simpler way to express it, that also takes into account possible return values, is to use the hyper operator.

class Die {
   has $.value;

   method roll {
      $!value = (1..6).pick;
   }
}

my @dice = [ Die.new, Die.new, Die.new ];
@dice».roll;
say "Rolled: ", @dice».value;

The Perl 6 language definition states that the .roll and .value calls may be called out-of-order, in a (currently theoretical) implementation that can parallelise the calls. But the final collated return values will be in the correct order. Yes, we are going to have really, really easy data parallelism. It's so cool.

Crossing another off the list

Finally, we get to the metaoperator that determined what my next (current) project will be. The Cross metaoperator, X. Its use is simplicity itself - you have two lists of things, and you want to apply the operator to each possible permutation of entries between them. Want to simulate the results of rolling two six-sided dice? Check this out:-

my @d6 = 1..6;
say (@d6 X+ @d6).sort;
$ perl6 2d6.p6 
(2 3 3 4 4 4 5 5 5 5 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 9 9 9 9 10 10 10 11 11 12)

It's really that easy. This will make all sorts of vector maths more trivial to do, and more importantly, it's given me an idea for a module I could write. I'll eventually get less busy again (I hope) and be able to blog more about its development, explaining some design decisions I made and pitfalls I hit, but as a sneak preview I've already uploaded the first beta release of Dice::Roller to Bitbucket, and also to GitHub because I need to do that to incorporate it into the Perl 6 module ecosystem even though I vastly prefer to use Mercurial and Bitbucket. I hope to make it a lot more detailed, but the basics of rolling dice are in there and usable!

No comments:

Post a Comment