Strangely Consistent

Theory, practice, and languages, braided together

Idiomatic Perl 6

So, I wrote a program to generate Pascal's triangle. The first ten rows of the triangle, at least. It only used simple features of Perl 6, such as scalars, nested arrays, and for loops.

my $ELEMENTS = 10;
my @pascal = [1];

for 1 .. $ELEMENTS - 1 {
    my @last = @pascal[ * - 1 ].list;

    my @current;
    push @current, @last[0];
    for 0 .. @last - 2 {
        push @current, @last[$_] + @last[$_ + 1];
    }
    push @current, @last[ * - 1 ];

    push @pascal, [@current];
}

say @pascal.perl;

In fact, save for simple mechanically substitutable differences, it could have been a Perl 5 script. In fact, with a bit of manual array allocation, it could have been a C script. That's OK; there's a tolerance in the Perl community of writing code that looks like it was thunk in some other language.

But I've heard that Perl 6 is great at doing things with operators. For example, the Z operator, which interleaves two lists, seems to be able to help me write my push statements more succinctly:

my $ELEMENTS = 10;
my @pascal = [1];

for 1 .. $ELEMENTS - 1 {
    my @last = @pascal[ * - 1 ].list;

    my @current;
    for (0, @last) Z (@last, 0) -> $left, $right {

push @current, $left + $right;

}
push @pascal, [@current]; } say @pascal.perl;

The parentheses before and after the infix:<Z> aren't necessary, because the Z operator has looser precedence than comma. They're just shown here to make your eyes accustomed to reading this construct.

In fact, now that only the addition is performed in the inner loop, I might as well use the Z+ operator, which does this for me.

my $ELEMENTS = 10;
my @pascal = [1];

for 1 .. $ELEMENTS - 1 {
    my @last = @pascal[ * - 1 ].list;

    my @current = 0, @last Z+ @last, 0; 

    push @pascal, [@current];
}

say @pascal.perl;

Now as the remaining loop shrinks to a size I can take in all at once, I see a bit more clearly what I'm doing: I'm building each new list from the previous one. I could feed the previous list into a named function to get the current one:

my $ELEMENTS = 10;
my @pascal = [1];
 

sub next-list(@p) {

[0, @p Z+ @p, 0]

}

for 1 .. $ELEMENTS - 1 { my @last = @pascal[ * - 1 ].list; my @current = next-list(@last); push @pascal, @current; } say @pascal.perl;

Or I could just feed it into a in-place anonymous sub.

my $ELEMENTS = 10;
my @pascal = [1];

for 1 .. $ELEMENTS - 1 {
    my @last = @pascal[ * - 1 ].list;

    push @pascal, (sub (@p) { [0, @p Z+ @p, 0] }).(@last);
}

say @pascal.perl;

But why even a sub? Perl 6 has a lighter construct, namely a "pointy block" (also known as a "closure" or a "lambda"). It doesn't participate in the call stack, and it's slightly easier to write.

my $ELEMENTS = 10;
my @pascal = [1];

for 1 .. $ELEMENTS - 1 {
    my @last = @pascal[ * - 1 ].list;

    push @pascal, (-> @p { [0, @p Z+ @p, 0] }).(@last);
}

say @pascal.perl;

Let's look at what the code does. Seed with one element. Calculate the next element based on the previous one. Stop at some point.

But that's exactly what the series operator does. The one that's written with three dots. We have a starting value, a way to get from one value to the next (our code block above), and a stopping value.

Well actually, we don't have the stopping value. But that's OK, since the series operator is lazy. So if we only request the first 10 values, it won't loop forever giving us the rest of the list.

my @pascal := do [1], -> @p { [0, @p Z+ @p, 0] } ... *; 

say @pascal[^10].perl;

(The extra do required because of a shortcoming in Rakudo.)

Now. Something very much like this code was posted first on Rosetta code and then on Moritz' blog. (TimToady used a sub, but said later that he'd have preferred binding.)

A couple of Perl 5 people's reactions were — somewhat uncharacteristically — of a negative flavour, similar to how people seem to react to the periodic table of operators:

@shadowcat_mst: an excellent example of why I consider camelia perl to be a language research project more than a production language

@pedromelo: I'm seriously considering this post as an example of what I don't want Perl6 to become...

I think these reactions are mainly feature shock. Higher-order operators, pointy blocks, and the series operator... they're all good, well-established features, which find daily use in Perl 6 programs. Maybe using them all together like that flung some people off the deep end. Never mind that the resulting script is all essential complexity, with virtually no boilerplate from the original script left.

This is the first time that's happened. I think it's important to listen to what Perl 5 people think and to try to respond to that. But I also think that this time, it's a case of them seeing some highly idiomatic Perl 6, and freaking out a bit.

And I think that that, in some odd sense, is a good thing. Well, not freaking people out, per se. But the fact that we did shows that there's something forming which might be tentatively called "idiomatic Perl 6": people on the inside can read it quite easily, but those on the outside, even Perl 5 folks looking in, instinctively go "eeeeew!".

That's OK. You're not meant to start with the idiomatic stuff. Language acquisition takes place step by step, and that goes for learning Perl 6 as well. On the way there, just don't confuse distaste with lack of familiarity.