DadHacker has recently posted about interviewing people that simply can’t name the “most interesting code they’ve read.” At work I mostly look at mediocre accounting application code, but I tend to try to stick to cutting SQL and parsing report text files. That doesn’t lead to a lot of interesting code reading. Books give you the basics of syntax and concepts… but the pressures of the real world are very different. There’s definitely a hole in my education here, so it’s time to start surfing source code files instead of programming blogs. CPAN makes it easy. Let’s see what we can turn up today….
Starting with something simple that I’ve implemented many times myself, let’s look at the dice rolling code first….
Philip Newton’s Games::Dice is pretty nifty. His regex is broken down with the /x option to make it easy to understand. I like how he uses ‘||’ to default functions that don’t pull anything from the regex:
$sign = $2 || ”;
In the past I’ve used two or three lines to do the same thing. It might be cryptic to certain classes of non-Perl programmers, but it seems to be typical of more idiomatic Perl. Another thing you see him do with his function variables is declare them all at the top. (I tend to declare them willy nilly….) Finally, you see him explicitly returning undef if the argument criteria aren’t met. I’ll keep that one in my ditty bag….
David Cantrell’s Games::Dice::Advanced uses a pretty flexible object oriented style interface. He blithely tosses in a folding operation in there:
sub foldl {
my($f, $z, @xs) = @_;
$z = $f->($z, $_) foreach(@xs);
return $z;
}
sub sum { foldl(sub { shift() + shift(); }, @_); }
print "This sum is ... " . sum(1, 2, 3, 4) . "\n";
To make sure I understood what this one was doing I wrote a little function to illuminate what was happening:
sub visible_sum {
foldl( sub { my $x = shift;
my $y = shift;
print "adding $x and $y\n";
return $x + $y; },
@_);
}
visible_sum(1, 2, 3, 4);
Here’s a gratuitous attempt to emulate the style of SICP in Perl:
sub myfold {
my($function, $first, @rest) = @_;
return $first unless @rest;
return myfold($function,
$function->($first, shift(@rest)),
@rest);
}
my $add = sub { $_[0] + $_[1]; };
print "myfold returns " . myfold( $add,
1, 2, 3, 4)
. "\n";
Just messing around…. I like how you basically get first and rest functions for “free”. The anonymous function syntax of Perl is perfectly workable, but not very pretty. I checked through Higher Order Perl hoping to find something better than the shifting and the array references we used above, but was disappointed. Sigh. You can’t have it all.
I like David’s idea about making reusable dice objects. Without taking a lot of time to try to read it, I’m not going to get around to understanding his code, though. I’m not too fond of ‘bless’ or Perl’s object system in general…. On the other hand, Philip’s code is running the regex to parse the dice text every single time you call his functions. Hmm….
OOP is overkill here. If ever there’s a time to trot out closures, this is it. In my apps, I’d probably steal Philip’s well-documented regex’s and roll them out with something like this:
sub roll_maker ($) {
my($line, $num, $type);
$line = shift;
return undef unless $line =~ m{
^ # beginning of line
(\d+)? # optional count in $1
[dD] # 'd' for dice
( # type of dice in $2:
\d+ # either one or more digits
| # or
% # a percent sign for d% = d100
)
}x; # whitespace allowed
$num = $1 || 1;
$type = $2;
$type = 100 if $type eq '%';
return sub {
my $result;
for( 1 .. $num ) {
$result += int (rand $type) + 1;
}
return $result;
}
}
my $roller = roll_maker("3d6");
print "Roll 3d6: " . $roller->() . "\n";
Philip Newton’s code allows for some customization by taking on an expression to modify it. It seems strange to have to write an impromptu parser just for this. I think we should use the regex to validate what comes into the function, here… but this may be an acceptable use of eval. Just like the closure above saving the function definition for later, we’ll use eval just a tiny tiny bit here and save it’s results for later use, too. (This needs a couple of tweeks– I’m only handling + and * properly just yet…. This is just to show the idea…. Don’t have time to fix this, now….)
sub define_sub {
my $text = shift;
return eval "sub { $text }";
}
sub roll_expression_maker ($) {
my($line, $dice_string, $sign, $offset, $sum, $roller, $exp);
$line = shift;
return undef unless $line =~ m{
^ # beginning of line
( # dice string in $1
(?:\d+)? # optional count
[dD] # 'd' for dice
(?: # type of dice:
\d+ # either one or more digits
| # or
% # a percent sign for d% = d100
)
)
(?: # grouping-only parens
([-+xX*/bB]) # a + - * / b(est) in $2
(\d+) # an offset in $3
)? # both of those last are optional
}x; # whitespace allowed
$dice_string = $1;
$sign = $2 || '';
$offset = $3 || 0;
$sign = lc $sign;
$roller = roll_maker( $dice_string );
return undef unless $roller;
$exp = define_sub('my $arg = shift; return $arg ' . $sign . ' ' . $offset . ';');
return undef unless $roller;
return sub { $exp->($roller->()); };
}
my $funny = roll_expression_maker("3d6+20");
print "funny " . $funny->() . "\n";
If we’re calling these functions a lot with the same types of requests, then it would make sense to memoize them. That would cut back on the times we parse these regex’s and run the evil eval….