In one of my early blog entries, "
Simple print-and-log subroutine", I shared a small piece of code that has been a nice, every-day tool - in Perl 5.
Today, I set about converting that to a naïve Perl 6 version, and being quite the Perl 6 n00b still, I ran into a few hurdles along the way.
The hurdles were easy enough to avoid, once
masak++ and
moritz++ had bonked my head sufficiently.
I'll walk through the code, step by step, to illustrate what I learned today, and what others might need to know.
use v6;
Ah, first, remember this statement. It's a nice hint for other software if you want to continue using the
.pl
file suffix instead of
.p6l
etc.
my $verbose = 1;
my $logfile = "/tmp/test.log";
my $*PREFIX = "plog";
Variables starting with the
twigil $*
is what we call
contextual variables, and
they just started working yesterday in Rakudo. Contextual variables follow a dynamic call path. When we reuse this variable later, it will depend on which call path we followed.
sub plogwarn ($msg)
{
my $*PREFIX = "plogwarn";
$*PREFIX now has a different value only for the cases where we've called the
plogwarn
subroutine, and the subsequent call to
plog
inherits this value.
plog $msg,1;
Oh, BTW, it's really, REALLY important to keep track of where you add whitespace or not. I'd been sleeping and class, and forgotten completely that
plog($msg,1)
would call
plog
with the parameters
$msg
and
$1
, while
plog ($msg,1)
would call
plog
with the single parameter
($msg,$1)
(yep, a list). It may be safer to avoid parentheses altogether when you're not dealing with lists - except when you have to.
}
sub plogdie ($msg)
{
my $*PREFIX = "plogdie";
plog $msg,2;
exit 1;
}
sub plog ($msg is copy, $level?)
The
trait is copy
means that I want to make a copy of the incoming parameter
$msg
, so that I can modify it inside of
plog
. Normally, parameters in Perl 6 are read-only and pass-by-reference (though not "reference" as you know it from Perl); the parameter as named is merely an alias for the actual variable. I can change the parameter in the caller if I use
is rw
, but that's not what I want to do here. The question mark in the second parameter -
$level?
- means that the parameter is optional, and I've used that in both
plogwarn
and
plogdie
above.
{
my $dt = Time.gmtime.date.iso8601
~ " "
~ Time.gmtime.time.iso8601;
Normally, I'd use
localtime
, but this is NYI
(not yet implemented) in
Temporal.pm
.
$msg = ($dt,"[$*PREFIX]",$msg).join(" ");
Here, I abuse that
is copy
to save myself one precious variable, just as an example.
if $verbose {
given $level {
when 1 { warn $msg; }
when 2 { die $msg; }
default { say $msg; }
}
The
given…when
construct is similar to the
switch…case
construct in other languages, but subtly different, as it allows more possible values and value types than most. Coming from a world of Perl 5.8 and older, this is simply lovely.
}
# Append message to $logfile
my $*OUT = open $logfile, :a;
Here's another use of a contextual variable, but this time it's the one normally associated with STDOUT.
print FILEHANDLE $msg
is no longer necessary, because you can always assign $*OUT in its own scope. The
open
statement has
:a
to indicate that I'm opening for append (so the syntax is less unixy than Perl 5's
>>
).
say $msg;
$*OUT.close;
}
This is how we can use the three subroutines, and the output should illustrate the different contextuals nicely:
plog("Oh, hello, there, sweetie.");
plogwarn("Consider yourself warned");
plogdie("Die, frackin' monster, die!");
…outputs…
2009-09-06 19:59:32 [plog] Oh, hello, there, sweetie.
2009-09-06 19:59:32 [plogwarn] Consider yourself warned
2009-09-06 19:59:32 [plogdie] Die, frackin' monster, die!
And here's the complete example code, which works with the current Rakudo:
use v6;
my $verbose = 1;
my $logfile = "/tmp/test.log";
my $*PREFIX = "plog";
sub plogwarn ($msg)
{
my $*PREFIX = "plogwarn";
plog $msg,1;
}
sub plogdie ($msg)
{
my $*PREFIX = "plogdie";
plog $msg,2;
exit 1;
}
sub plog ($msg is copy, $level?)
{
my $dt = Time.gmtime.date.iso8601
~ " "
~ Time.gmtime.time.iso8601;
$msg = ($dt,"[$*PREFIX]",$msg).join(" ");
if $verbose {
given $level {
when 1 { warn $msg; }
when 2 { die $msg; }
default { say $msg; }
}
}
# Append message to $logfile
my $*OUT = open $logfile, :a;
say $msg;
$*OUT.close;
}