Sunday, August 9, 2009

Some ways that Perl 6 is grand, part 1 of ?

(After YAPC::Europe in Lisbon, we no longer say "awesome", we say "grand". ;))

In Lisbon, there were several talks that aided us to a better understanding of how Perl 6 may be more pleasant and useful than Perl 5.8, or even Perl 5.10.

I thought I'd try to illuminate some of these as I progress in my own knowledge of Perl 6.

First choice: the zip operator and the new quoting syntax for generating lists and selecting items from hashes:

my @months = <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>;
my %days = @months Z 31,28,31,30,31,30,31,31,30,31,30,31;
say "Days in March: " ~ %days<Mar>;
say "Days in June: {%days<Jun>}";
say ~%days;
That was quite a bit in one go. :) First, I create a new list of abbreviated month names. Then I combine the list of months with a list of days in that month (for 2009, obviously) using the zip (Z) operator, and print the number of days in March and June. Finally, I pretty-print each month with the corresponding number of days.

The result looks like this:

Days in March: 31
Days in June: 30
Jan 31
Feb 28
Mar 31
Apr 30
May 31
Jun 30
Jul 31
Aug 31
Sep 30
Oct 31
Nov 30
Dec 31
Now imagine what that would have to look like in plain Perl 5.

There are several new things here that makes your programming days easier:
  1. The well-known sigils $, @ and % are now used in a consistent manner. @ signifies a list, % signifies a hash, also when you access items by index.
  2. Interpolation of non-scalars is now handled with curly brackets {}.
  3. Building a list can be done using commas, no parentheses are necessary.
  4. Building a quoted list can be done using angle brackets, which are auto-quoting.
  5. Accessing a hash item is done using angle brackets, which should be welcome to those of use who don't use US/UK keyboards (bye-bye to {'argh'}, but it still works).
  6. say is a new pretty-printing friend, which automatically adds newlines.
  7. The concatenation operator is now tilde (~) instead of a period.
  8. As a unary prefix operator, tilde by default stringifies a hash pair-wise with newline separators (while a list is stringified with space separators). It is possible to change this behaviour in derived subclasses.

Edit 2009-08-10 08:34-09:18 UTC: Clarified and added interpolation.

15 comments:

Chas. Owens said...

An interesting upshot of number one is that you no longer have to explicitly dereference arrayrefs and hashrefs to index into them:

$scalar[0]

now means dereference the array reference in $scalar and return the first item.

$scalar<key>

and

$scalar{"key"}

return the value for "key" in the hash reference stored in $scalar.

raiph mellor said...

Moritz Lenz wrote a series of blog posts about P6 features for those with a P5 background. They are a bit old (started 2008) and are thus a bit off here and there due to language evolution, and they sometimes cover a lot of ground quickly (at least for me), but are the best series I've encountered so far.

Robert 'phaylon' Sedlacek said...

No need to imagine the Perl 5 version:

use 5.10.0;
use List::MoreUtils qw( zip );

my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
my %days = zip @months, @{ [31,28,31,30,31,30,31,31,30,31,30,31] };

say "Days in March: " . $days{Mar};
say "Days in June: $days{Jun};
say while $_ = join ' ', each %days;

bakkushan said...

Please note that the 5.10.x version should have a tab in the join(), not a regular space:

say while $_ = join '\t', each %days;

Perl 5.10 has borrowed quite a few nice things from Perl 6, and that helps.

But even with the simplified use you have in 5.10, there's still a bit more typing and syntax elements to remember - @{[ list elements ]} - and an explicit while loop, FCOL ...

Chas. Owens said...

say while $_ = join ' ', each %days;

is not the same as

say ~%days

A closer, but not perfect, translation would be

say $_, " " x (8 - length() % 8), $days{$_}, "\n" for keys %days;

Chas. Owens said...

@bakkushan those aren't tabs, they are spaces:

./perl6 -e "my %h=<a b c> Z 1 .. 3;print ~%h;" | cat -e

By the way, do you know where the behavior of ~%h is spec'ed? I keep getting the keys back in the same order I put them in and don't know if that is an implementation quirk or a feature.

bakkushan said...

See the synopsis for Str.fmt for more information about the default formats and separators used.

bakkushan said...

Try this:

echo -e 'Foo\tbar' | cat -e

As you can see, tabs aren't escaped with cat -e. That is, tabs aren't considered non-printing characters.

bakkushan said...

Regarding the order of elements in a hash, the synopsis says that successive calls to keys, kv, pairs and values will have the same order of elements, unless the hash has been changed.

I don't see any specific mention that the order of the elements is the same as at creation, but I suspect that creation counts as the first in a series of successive calls. I'll ask.

Robert 'phaylon' Sedlacek said...

I didn't know the issue was an exact replication of what seemed to be debugging code. If you really cared about the output format, would you really be as implicit as "say ~%days"?

Chas. Owens said...

@bakkushan You are correct,

./perl6 -e "my %h=<a b c> Z 1 .. 3;print ~%h;" | perl -pe 's/\t/\\t/'

Shows that those are tabs, which makes the Perl 5 code simpler:

say map { "$_\t$days{$_}\n" } keys %days;

@Robert 'phaylon' Sedlacek saying

say ~%days

as debug code would be foolish. How would you differentiate foo => "bar\tbaz" from "foo\tbar" => "baz"? In Perl 6, you would say

%h.perl.say;

to debug. The perl method's output is similar to Data::Dumper with terse and useqq turned on:

{"c" => 1, "b" => 2, "a" => 3, "f" => 4, "z" => 5}

or

%h.fmt("[%s] => [%s]").say;

which is roughly equivalent to

say map { "[$_] => [$h{$_}]" } keys %h;

Robert 'phaylon' Sedlacek said...

Alright, then what problem does the example solve? :)

Matt said...

Given Moose::Autobox I'd write -

%h->perl->say;

for debugging too.

I'd also note that you don't need a zip and IMO it's actually less clear what you're doing than -

my %days; @days{@months} = (31,28,31,30,31,30,31,31,30,31,30,31);

(anybody yelling "but hash slices are weird" gets me yelling "no more so than a zip operator" back followed by a custard pie)

-- mst

Chas. Owens said...

@Robert 'phaylon' Sedlacek I take the author's words as the purpose: "pretty-print each month with the corresponding number of days"

@Matt I like perl5i's version of ->perl better (and not just because I wrote autobox::dump). It looks like Moose::Autobox just does a straight Dumper call. autobox::dump turns on several features of Data::Dumper like useqq, terse, indent (level 1) that makes the output much nicer. It also enables a rudimentary dumping of coderefs:

print sub { print "foo" }->perl;

produces
sub {
    use warnings;
    use strict 'refs';
    BEGIN {
        $^H{'autobox_scope'} = q(8454608);
        $^H{'autobox'} = q(HASH(0x83e630));
        $^H{'autobox_leave'} = q(Scope::Guard=ARRAY(0x83e7c0));
    }
    print 'foo';
}

instead of Moose::Autobox's

$VAR1 = sub { "DUMMY" };

bakkushan said...

Thanks for your comments, everyone.

The purpose of the post is to showcase some features of Perl 6.

The example was something I coughed up in a few minutes, and I probably could have chosen something else to do so. :)

Regarding the clarity of hash slices vs. the zip operator, that's a matter of what you're used to more than something intrinsic to the presentation itself, in my opinion.

Personally, I'm not overly fond of the use of the bareword character Z for this purpose.