Trying to progressively convert some Perl scripts to Raku. I am quite stuck with the following one, even after browsing quite a lot here and reading Learning Perl 6 more deeply.
The part on which I can’t make progress is the last loop (converted to for
); getting keys and sorting them by month name and day number looks impossible, but I am sure it is doable.
Any hints on how to achieve this with "idiomatic" syntax would be really welcome.
#!/usr/bin/perl
use strict;
my %totals;
while (<>) {
if (/redis/ and /Partial/) {
my($f1, $f2) = split(' ');
my $w = $f1 . ' ' . $f2;
$totals{$w}++;
}
}
my %m = ("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
foreach my $e (sort { my($a1, $a2) = split(' ', $a) ; my($b1, $b2) = split(' ', $b) ;
$m{lc $a1} <=> $m{lc $b1} or $a2 <=> $b2 } keys %totals) {
print "$e", " ", $totals{$e}, "n";
}
5
Answers
You could try something like:
Fed with the same sample data, your perl code produces the same output as this.
I think that this is close to what you are asking for… also shows that perl6/raku is quite closely related to perl5 unless you want to get fancy…
The main changes are:
TL;DR @wamba provides an idiomatic solution. This answer is a minimal "mechanical" translation instead.
I think your question and this answer suggests that a great way to learn many of Raku’s basics as they relate to Perl is:
Feed a small Perl program into Rakudo;
Methodically investigate/fix each reported error until it works;
Post a question to StackOverflow if you get stuck.
Presuming that’s what you did, great. If not, hopefully this answer will inspire you or other readers to try doing just that. π
The code
works for the following test input:
The mechanical translation process
I began properly working on your question by feeding the code in your question to Rakudo. And was surprised to get
Unsupported use of <>. ...
. It was Perl code, not Rakunian! πThen I saw @wamba had provided an idiomatic solution. I decided I’d do the most direct translation possible instead. My first attempt worked. Order restored. π
I pondered how best to explain my changes. I wondered what the error messages would be if I went back to the start and just fixed one at a time. The result was a delightful series of good error messages. So I’ve structured the rest of this answer as a series of error messages/fixes/discussions, each one leading to the next, until the program just works.
In the interests of simplicity I drop most of the info from the error messages. The messages/fixes are in the order I encountered them by fixing one at a time:
(
β
is Unicode’s eject symbol, marking the point where the compiler conceptually "ejects" the code.)The idiomatic replacement of Perl’s
while (<>)
isfor lines()
.Raku interprets code of the form
foo(...)
as a function call if it’s where a function call makes sense. This takes priority over interpretingfoo
as a keyword (i.e.my
as a variable declarator).Next, because
my($f1, $f2)
is interpreted as a function call, the$f1
is interpreted as an argument that you haven’t declared, leading to the error message.Inserting whitespace after the
my
fixes both the real problem and this apparent one.(This error occurred in multiple locations in your code; I applied the same fix each time.)
To help remember that
~
is used as a string operation in Raku, note that it looks like a piece of string. π§΅As Damian Conway notes, "We took this Perl table of what do you use when, and we made it this table instead".
The code
$totals{...}
is syntactically valid. One can bind or assign a hash (reference) to a scalar. But Rakudo (the Raku compiler) knows at compile time that the code hasn’t declared a$totals
variable, so it rightly complains.Your code has declared a
%totals
variable, so Rakudo helpfully asks if you meant that.(This error occurred in multiple locations in your code; I applied the same fix each time.)
Raku code tends to be shorter (and more readable) than Perl code. It’s mostly due to design that goes beyond mere paint, but little things like
s/for/foreach
don’t hurt.This error message is arguably LTA (I’m thinking "non-descriptive"). But it’s equally arguably pretty good, all things considered.
Perl and Raku support binding of a value to a new lexical variable/parameter scoped to a block. Perl uses
my
, and puts the variable(s) in front of the value(s). Raku puts the value(s) first, inserts a->
between them and any variables, and skips themy
.(There’s a good deal of richness to this use of
->
that I’ll not get into here because it doesn’t matter for this example. But it’s worth being aware that this change buys Rakoons a good deal, and you’ve got that to look forward to.)As Perl devs know, it has special variables
$a
and$b
.Raku generalized this notion to
$^foo
parameters, a convenient DRY way to add positional parameters to a closure while skipping the usual ceremony in which one has to specify the name twice (once to declare it, another time to use it).An unusual aspect of these that might initially seem crazy, but is actually very ergonomic, is that their formal parameter position is determined by their name. So, given
$^foo
and$^bar
, the latter will bind to the first positional argument,$^foo
to the second.I inserted a comma where indicated.
Some Perl routines implicitly presume use of
$_
. There’s no syntactic way to know whether or not any given routine is implicitly using it. You just have to read each routine’s definition. Raku dropped that.So Rakudo concludes the
split
routine is missing the string that’s to be split.(The
|
in the "signature of the proto ($, $, |)" just means "other arguments that can optionally be passed", so you can ignore that. The$, $
indicates two arguments are required, so we’re missing one.)A quick check of the routine definition shows the
sub
version ofsplit
requires the string to be split as its second positional argument. Thus I switched tosplit(' ', $_)
.The code works. o/
Notably, all the actual error messages started with
===SORRY!=== Error while compiling
. That means they were all caught before the program even ran, which is nice. πYou’ve already got good answers, but I’m taking the opportunity to expose you to some other standard Raku tools and idioms that seemed natural to me for your problem.
For both my solutions:
My equivalents of your
%totals
variable store keys in a structured data form rather than just as string keys. The supposed rationale is to simplify the sort and presentation. (But really it’s to show you another way. It would of course be trivial to ensure the month and day numbers are concatenated as two two digit numbers to ensure correct sorting.) I use two different key types to show variations on this theme.I deal with conversion to/from month names by constructing hashes mapping names to numbers. I declare one with the
.pairs
or.antipairs
method, and then apply the reverse to convert in the other direction. I do this one way in the first solution and the other in the second. And I set the number forjan
to0
in one solution and1
in the other.Short and sweet, lean on
Pair
sWhen declaring a
%foo
variable, if you don’t specify its key type, it defaults toStr
. But in this code, the key of eachPair
in%totals
is itself aPair
:If no sort closure(s) are specified, Raku’s
sort
routine, when applied to a hash, sorts its entries by comparing their keys usingcmp
. Furthermore, for an ordinary hash, comparing two keys means comparing two strings.That would work fine for your situation if these strings were each date’s month and day formatted as two digits each and then concatenated. Alternatively, splitting and schwartzian works fine too. Raku’s really good at that stuff but I preferred to go a different way with this answer, so that the default sort did the right thing.
For this first solution, I picked
Pair
s as the key type. Whencmp
comparesPair
s, it sorts first by key and then by value within that. Both key and value were coerced toInt
s, thus the above code correctly sorts by month, then days within that.More structure, use
Date
sThis version adds structure and more fancy typing. It wraps the equivalent of the
%totals
hash (renamed%.data
) into an outer object containing some utility routines, and makes the inner key object be aDate
instead of aPair
:In the first solution,
sort
did the right thing because it was comparingPair
s, andcmp
in turn did the right thing given how I’d set the pairs up.In this solution
sort
/cmp
do the right thing without needing to coerce string values toInt
s, because the totals entries areDate
s and they compare according to ordinary date comparison rules.