Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

masak (6289)

masak
  (email not shown publicly)
http://masak.org/carl

Been programming Perl since 2001. Found Perl 6 somewhere around 2004, and fell in love. Now developing November (a Perl 6 wiki), Druid (a Perl 6 board game), pls (a Perl 6 project installer), GGE (a regex engine), and Yapsi (a Perl 6 implementation). Heavy user of and irregular committer to Rakudo.

Journal of masak (6289)

Thursday August 26, 2010
07:22 PM

Idiomatic Perl 6

[ #40516 ]

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.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Wrong Rosettacode link, it should point to http://rosettacode.org/wiki/Pascal's_triangle#Perl_6 [rosettacode.org].
  • You seriously consider this "idiomatic" solution more elegant? If that is what idiomatic Perl 6 looks like, it will never take off ... Sorry, but that is as unreadable as it could possibly get and just fuels the unstoppable line-noise argument even more. Good languages _can_ be descriptive, self-documenting and to a degree self-explanatory. I always thought that one of Perl's philosophies was to be more like a human language. Guess I was wrong, it's math after all ...
    • I always thought that one of Perl's philosophies was to be more like a human language. Guess I was wrong, it's math after all ...

      In some sense, I think it's entirely appropriate that solving a problem from (arguably) a mathematical domain ends up with a solution that looks mathematical. Just because this particular problem ends up with a mathematical-looking answer in Perl 6 doesn't mean that all Perl 6 programs will look like mathematics. Answers to problems in other domains will tend to look like the

  • It's nice to see that Perl6 will be on par with Haskell for these kinds of examples. This Haskell example is from Rosseta code page (but rewritten for similarity).

    pascal = iterate (\row -> zipWith (+) ([0]++row) (row++[0])) [1]

    The essential complexity of this example is the same. Only it contains more words than operators.

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

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

    FWIW, I think parentheses will work here also.

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

    Pm

    • Aye. I considered both forms, and went with the do form.

      When binding has list precedence, neither parentheses nor the do will be needed.

  • I really like this. I liked it even more as I worked through all of the examples to better understand them. One thing I would change:

    say $_.perl for @pascal;

    The output is much clearer that way (at least to me). I actually tried to get it a touch more "triangly":

    my @pascal := do [1], -> @p { [0,@p Z+ @p,0] } ... *;
    say ' ' x 2 * (10 - $_.elems), $_.perl for @pascal[^10];

    But wound up with this:

                      [1]
               

    • my $n = 10;
      my @pascal := do [1], -> @p { [0,@p Z+ @p,0] } ... *;
      my $m = @pascal[$n - 1].perl.chars;
      say (my $r = $_.perl).fmt("%{($m + $r.chars) div 2}s") for @pascal[^$n];

      Produces this:

                       [1]
                      [1, 1]
                    [1, 2, 1]