2012/04/05

use Perl6; A few very welcome changes in Perl5++

In the 10 years I've been programming Perl off and on, I've heard a fair amount about Perl 6. There are those who love it, and those who dislike (fear?) it. For me, I had always wanted to look further into it but never found the time. Don't get me wrong, I absolutely love Perl 5, and will likely be using it until we see the day that it fades into the same level of obscurity that some of my code resembles.

Over the last couple of weeks, I've been constantly tempted to follow the Perl6 link in moritz's PerlMonks signature. Yesterday I broke down and decided to see what colour I wanted my bikeshed. Here are a few of the really interesting differences I've found so far.

In this post, I'll touch on strict, sigils, how variables are objects and have methods, types, and a bit on control structures. In a couple following posts, I'll describe the basics of other changes, and then get into more advanced aspects of the new language. When I'm comfortable enough and can change as much as possible from 5 to 6, in my last post on the subject, I'll include the code of one of my short Perl 5 modules translated into Perl6.

STRICT

Out of the box, the first really nice feature is that strict is enabled by default.

% cat no_strict.pl

#!/home/steve/perl6/perl6
say $hello;

Output:

% ./no_strict.pl

===SORRY!===
Variable $hello is not declared
at ./no_strict.pl:2

SIGILS

In Perl6, variables retain their sigils regardless of what operation you perform on them. To access an element of an array or the value of a hash in Perl 5, you had to use the scalar sigil to signify you intend to access it as such:

Perl 5 way:

my @a = qw( 1, 2, 3 );
say $a[0];

my %h = ( key => 'value' );
say $h{ key };

But in Perl6:

my @a = 1, 2, 3;
say @a[0];

my %h = 'key' => 'value';
say %h{ key };
Output:
===SORRY!===
CHECK FAILED:
Undefined routine '&key' called (line 7)

Oh, oh! What happened? The array portion of the code is fine, but we broke at the hash code. Well, in Perl6, hash keys are not automatically quoted like they are in Perl 5 when attempting to access the hash values. Instead of retrieving the value, it attempts to call the sub key(), looking for it to return the name of the key to be used. The proper way to access the hash values through a key is as such:

# the old faithful

say %h{ 'key' };

# or the new auto-quote syntax

say %h< key >;

VARIABLES ARE OBJECTS (and have methods)

Here are a few examples of the new variable object methods in action, and their corresponding perl 5 syntax (which still works in Perl6). I'll show a few examples of arrays first, then hashes. Also worth noting is the lack of parens around the array elements in the definition. Surrounding the elements in parens is still valid, but the qw() function is missing.

Variable methods, arrays

my @a = 2, 3, 1;

# number of array elements

say @a.elems;
say scalar @a;

# sort array

say @a.sort;
say sort @a;

# map array

say @a.map({ $_ + 10 });
say map { $_ + 10, ' ' } @a;

# or even

say @a.sort.map({ $_ + 10 });
say map { $_ + 10, ' ' } sort @a;

I found an interesting difference while building those code examples. In Perl 5:

perl -E 'my @a=qw( 1 2 3 ); my $x=@a; say $x'
3

...but in Perl6:

perl6 -e 'my @a=1,2,3; my $x=@a; say $x'
1 2 3

However, using the array in numeric comparisons evaluates the array as its number of elements:

perl6 -e 'my @a=1,2,3; say "ok" if @a == 3'
ok

Variable methods, hashes and their Perl 5 syntax counterparts

my %h = z => 26, b => 5, c => 2, a => 9;

say %h.keys;
say $_ for keys %h;
# could also be written as:
say keys %h; # but the spacing is different in 5

say %h.values;
say $_ for values %h;

say %h.keys.sort;
say $_ for sort keys %h;

Note: Most of the variable object methods also still act as functions, so the following are equivalent:

say %h.keys;
say keys %h;

EVERYTHING IS AN OBJECT, AND HAS A TYPE (and can optionally be constrained)

To give an extremely clear example of how everything is an object and has a type before I get further into how types are handy, I'll use some syntax that I tried and was surprised that it worked. The WHAT() method when called on something informs you of its type.

# calling methods on literals w00t! :)

say 25.WHAT;
say 'string'.WHAT;
say (1,2,3).WHAT;

Output:

Int()
Str()
Parcel()

We can do simple type checking:

my $quote = "I am liking Perl6";

if $quote ~~ Str {
    say "it's a string";
}

Note the lack of parens again, in the if() condition this time. More on this shortly. For now, just know that they can be used (but there are gotchas), but it is recommended that you don't use them.

Constraining variables to certain types is also easy.

# define $x as an Int
my Int $x = 5;

# try to assign it a string
$x = "Hello, world!";

Output:

Type check failed in assignment to '$x'; expected 'Int' but got 'Str'
  in block  at ./types.pl:15

Types have an inheritance hierarchy, but I am not too familiar with it yet. I'll update this post as I learn more. For example, an Int is a subclass of Numeric.

CONTROL STRUCTURES

I briefly touched on using parens with the if statement above. Take this example:

my $x = 5;
if ($x < 10){
    # do stuff
}

In Perl6, having the parameter directly next to the opening parens with no whitespace tells the interpreter to try to call a function named 'if'. This is true for all of the control structures (if, while, for etc). If you leave at least one whitespace between the opening parens and the first character of the expression, things will work as normal. However, to protect against mistakes, it is advised you omit the parens entirely. Here are some interesting changes:

In Perl 5, for the most part, we'd use named lexicals in our for loops like this:

for my $elem ( @a ){ say $elem; }

In Perl6, to avoid use of $_, we use a "pointy block":

for @a -> $elem { say $elem; }

Because I've been testing each code snip before I paste it into this document, I of course just ensured that my Perl 5 for() example was written correctly lest my eyes miss something. Against perl6, the typical Perl 5 for structure above gave me this output:

===SORRY!===
This appears to be Perl 5 code
at ./control.pl:15

So it looks like the pointy block is the way forward. Another note about for(); it is now only used for lists. Perl6 separated the C-style for loop into a loop() structure.

It is now possible to use more than one loop variable:

for @a -> $first, $second, $third { 
    say "$first, $second, $third: I'm greedy on each iteration!"; 
}

Or iterate over a hash without a while/each

for %h.kv -> $k, $v {
    say "$k :: $v"
}

While I was throwing out the use of the kv() method, one of my readers who opted to remain Anonymous pointed out a fantastic feature that I had missed. kv() can be used against arrays as well as hashes. When used against an array, the key is the index number of the array, and the value is the contents of the element. How many times have you sighed at the fact that you have to declare an iteration scalar prior to a for() loop, and then waste another line increasing it upon each loop? No more! The first potential use case that came to me after reading Anon's comment was using the index of the array and the element to create a hash:

my @a = 'a', 'b', 'c';
my %h;

for @a.kv -> $index, $elem {
    %h{ $index } = $elem;
}

There are so many cases where I can think of that we can benefit from not having to define "$i = 0;" and then a second line "$i++". Two lines saved. If you are like me, you dislike using variables for temporary assignments.

Thanks for reading. I hope you enjoyed my little beginning venture into the world of Perl6.

For the most part, the resource I'm using to base my tests and code on can be found here.

20120405

Update 20120406: Thanks to Daniel Ruoso for clarifying what really happens when a bareword is used as a hash key.

Update 20120410: Thanks to a kind Anonymous reader who pointed out that I missed that kv() could be called against an array, and the details of its implementation. They also provided sample code to describe a use-case, which I used as a baseline to create my array kv() example. Cheers!

5 comments:

  1. Great post. On the "undefined rountine &key", it's not that it is trying to invoke key as a method on the hash, but it's rather trying to invoke key as a sub to get its return value to be used as the key of the hash.

    ReplyDelete
  2. Thank you very much for the feedback Daniel, it is this type of corrective feedback I desire. I'm just heading out the door right now, but I'll update the post as soon as I return. Cheers!

    ReplyDelete
  3. What you missed is .kv method on arrays that provides you both element and its index:

    my @a = "foo", "bar"; for @a.kv -> $index, $value { say $index, $value }

    No more $i++ :)

    ReplyDelete
  4. Cheers Anon!! I most certainly missed that use-case of kv, and within two seconds of reading your feedback, I had a boatload of uses for it already! Seriously... built in iterations! :)

    I'm going to update the post now.

    ReplyDelete
  5. Actually your last example should be writeable even shorter now:

    %h = @a.kv;

    ReplyDelete