The Magic of Perl6

April 28, 2016

Categories: Technical Tags: Programming Perl6

I have tried quite a number of programming languages, mostly out of curiosity than anything else. Even so, Perl was something I always steered clear of. Traumatic first experience, terrible reputation and all that. And apparently, so did a lot of people. It’s 2016, and Perl’s prominence isn’t close to what it used to be in its heyday. Fads pass, even empires fall. Except, it would be unwise to equate this with poor quality. No tool hits every sweet spot, that’s why we have so many of them. And adoption depends on so many other unrelated things. Think of npm, or don’t if you care about sanity.

Anyway all these are barely related to what I want to write about which is Perl 6, Larry Wall’s utopia. The major distraction that annoyed and divided original user base. Something that - to others - gradually attained the right to be elevated to the illustrious ranks of Duke Nukem Forever, NetHack, Half Life 3 or GNU Hurd. Years rolled by while several implementation attempts got abandoned, from the outside it looked exactly like a meme.

And it says a lot about the vision and dedication that it finally became a reality in the form of Rakudo at the end of 2015, a good 15 years after the first proposal. Granted it’s just the first release, as such refinements like speed of execution, library and community size, tool support etc. leaves a lot to be desired. But it’s feature complete with respect to the spec, and that’s really something to be talked about.

The nomenclature gives one the impression of Perl 6 being the evolution, and that might have been the original motivation, but it’s really not. It’s a full scale revolution. My limited exposure doesn’t make me an authority, but I can draw subjective comparison based on everything else that I have used. Perl 6 kinda borders on magic.

It’s a big language, lots of features. It’s modern and it’s multi-paradigm. What’s so beautiful about it is that every thing comes around to complement everything else so well. So you have an all powerful Swiss army knife, but this time it’s also elegant. To spare a few words on the features: it has gradual typing so you can mix and enjoy the best of both static and dynamic typing, concurrency/parallelism/async is built into the core and is accessible at a high level of abstraction, it has built in grammars and polished first-class regex, lazy evaluation, great OO model, full grapheme based unicode, clear scoping rules and closures, multiple dispatch based on signature, pattern matching and de-structuring, metaprogramming, native data types and easy interpolation with native code (you can even inline Perl 5 and python code), safety features like bounds check and rational numbers and so on. I don’t care for them all, and maybe you don’t either. But the point is, when the going gets tough, it will likely live upto the challenge.

There are lots of great and fancy up and coming languages out there. I have a keen interest in most of them. And beauty is in the eyes of beholder, so I won’t go there. But what almost all languages completely miss is the aspect of ergonomics, the attention to a programmer’s productivity as he solves a wide variety of problems. Maybe it’s the long years in the dark along with the amazing people behind the veil and their desire to get it right even if it’s very late and things are needed to worked out from the scratch yet again, but they got it right which is incredible given the breadth of it.

Give it a try for yourself. For quick intro:

Or if you prefer examples:

For a few appetizers that focuses more on the language, I solved the first few project euler problems (you would want to read the questions to follow along):

Problem 1
say [+] (^1000).grep: { !(($_ % 3) && ($_ % 5)) };

Things to note:

  • say is the print equivalent routine.
  • [] in this context is the reduce meta-operator which here took an infix operator(+). Acting on a list of numbers this basically does the job of sum. You might also know it as Fold.
  • The syntax for iterable range construction is 1..10. The range is inclusive, you may tell it to exclude the last element with 1..^10. ^10 is essentially a short hand for 0..^10.
  • The routine grep filters a sequence based on a pattern provided. The pattern is given in the form of a block which is akin to a closure (anonymous function with environment). The colon after grep allows you to pass the block without needing parenthesis, it’s a handy short-cut (explained here among others). So these two are the same: (1..10).grep({$_ mod 2 == 0}) and (1..10).grep: {$_ mod 2 == 0}. If you need to pass an argument to it, you can do so by -> $x { $x }. The $_ in { $_ } is just a syntactic sugar for the parameter passed.
  • The body of the function is just a cheap boolian trick.
Problem 2
sub fib($limit) {
    my ($last, $new) = 0, 1;
    while $last < $limit {
        take $last;
        ($last, $new) = ($new, $last + $new);
    }
}

say [+] (gather fib 4_000_000).grep: * %% 2;

Things to note:

  • sub is how you define functions. Nice bit of pattern matching in the variable definition and also inside the loop. Note that even the while loop works on a block, this is very prevalent construct.
  • The gather/take is the iterator builder pattern. Together you get the effect of laziness, somewhat like yield of Python. Note that the gather needs not be in the lexical scope of take, dynamic scope will suffice.
  • Underscores in numbers are allowed because they add readability.
  • (x %% y) is a short cut for (x mod y == 0).
  • And yes, then there is the *. It’s really such a simple concept, yet so bizarre. Despite that, it’s already one of my favourite aspects of the language. But before I explain, here is a ridiculous alternate solution:
say [+] (0, 1, * + * ...^ * > 4_000_000).grep: * %% 2;

That’s just bonkers! To de-mystify the * which in Perl 6 parlance is known as Whatever type. Basically:

Everything that can’t be categorized otherwise fits into the “Whatever” slot, or as the Perl 6 hacker would write it, *.

Essentially the Whatever-Star stands for whatever it makes sense in the context it appears in. So its semantics is context dependant. When used in (1..*) it constructs an infinite range. If you have an array named @x then @x[*-1] indexes the last item because * represents the length of the array. Or in (1..10).grep: * %% 2 it basically de-sugars to (1..10).grep: -> $x { $x %% 2}.

In this example above, it defined a series. Consider 1, * + 1 ... *. Here the first star represents the number just prior to it in the series, so basically it means the second number is one added to the first number. The second star means this series goes on to infinity. So 0, 1, * + * ... * is a series where every number is the sum of the past two numbers, and the first two numbers are 0 and 1. That’s what Fibonacci is! The second part * > 4_000_000 represents a goal, instead of infinity this now terminates the list after 4 million. ...^ makes this exclusive. You can think of it as takeWhile from Haskell. The result is an extremely elegant definition, you just describe what Fibonacci is!

Problem 3
sub largest-prime-factors($num is copy) {
    for 2, 3, *+2 ... sqrt $num {
        while $num %% $_ {
            $num div= $_;
            return $_ if $_ > $num;
        }
    }
}

say largest-prime-factors 600_851_475_143;

Not much of interest in here. By default parameters are immutable which is a nice default. Here I used is copy trait to make certain that the argument is copied, and therefore mutable. Also note the lack of parenthesis in the function call.

Problem 4
((999...800) X* (999...800)).grep({ .flip eq $_ }).max.say;

Things to note:

  • The X is an infix meta-operator for cartesian product. It can take another operator so X* multiplies each crossed together elements. I used lower bound 800 because otherwise the whole computation takes too long.
  • .flip is a sugar for $_.flip.
  • Perl 6 has UFCS (Uniform Function Call Syntax). You can chain different functions together as if you are calling methods.
Problem 5
say [lcm] 1..20;

Only thing to note is that there is a built-in lcm routine.

Problem 6
my $nums = 1..100;
say (([+] $nums) ** 2) - ([+] $nums.map({$_ * $_}));

Further use of $_, this time twice. Also map, and it behaves as expected.

That’s all for now. I sure could get used to writing this.