Day 7: Looping for fun and profit

Any programmer who’s ever used a language which has them probably knows that loops are incredibly useful. In languages which provide them, foreach loops for iterating over arrays or lists tend to end up being very common. In Perl 5, these loops were provided by the foreach keyword, although you could also write for, sharing a keyword with the more general C-style for loops.

In Perl 6, that’s all changed.

for is now exclusively for iterating over lists. foreach has disappeared, and C-style for loops are handled by the new keyword loop. We’re not going to discuss those today, but we are going to focus on the new for loop, which combines with some other Perl 6 language features to deliver an enormously flexible and powerful construct.

Let’s look at a basic case.

for 1, 2, 3, 4 { .say }

There some immediately noticeable things about the syntax here. There are no brackets around the list construction, which is something that extends throughout Perl 6. Generally, you need a lot fewer brackety characters than in Perl 5. Much like in Perl 5, the loop variable is $_ by default. The method call of say with no invocant is the same as saying $_.say. Note that in Perl 6, you cannot say say with no argument to default to $_, you have to use the .say form, or specify $_ explicitly.

The block doesn’t have to be an ordinary block. It can be a pointy block, which lets you name your loop variable using the pointy block’s parameter capability.

for 1, 2, 3, 4 -> $i { $i.say }

A pointy block is a bit like an anonymous subroutine, except it doesn’t catch return exceptions. If you call return inside a pointy block, the enclosing routine will return.

Pointy blocks can take more than one parameter in their parameter lists. What happens if you do that?

for 1, 2, 3, 4 -> $i, $j { "$i, $j".say }

Well, if you run it, you get:

1 2
3 4

So what’s actually happened is that you’ve iterated over the list two elements at a time. This works for any number of parameters, with the minimum of one, degenerating to using $_ if you provide no explicit parameters yourself.

Having realised we can do this, what can we do with the generation of the list we iterate over? Well of course, we can use an array variable:

for @array { .say }

Although in many simple cases, we might prefer to use map:

@array.map: *.say;

Or a hyperoperator, if order and sequentiality isn’t important:

@array».say;

But neither of those things are today’s subject.

We might generate a list of numbers using the range constructor &infix:<..>:

for 1..4 { .say }

It’s very common that we want to generate a list of $n numbers beginning with 0, such as array indicies. We could write 0..$n-1, or using a variant of the range constructor 0..^$n, but Perl 6 provides a handy shortcut in the form of prefix:<^>:

for ^4 { .say }

Which will output:

0
1
2
3

One reason people often fall back on C-style for loops in Perl 5 is because they need to know what index in the array they are at for each item, or because they need to iterate over two or more arrays in parallel. Perl 6 offers a shortcut here, with the infix:<Z> zip operator.

for @array1 Z @array2 -> $one, $two { ... }

Assuming the two arrays are the same length, $one will be each element of @array1 and $two will be the corresponding element of @array2. If they are different lengths, iteration will stop when the end of the shorter array is reached.

With this knowledge, and the awareness that Perl 6 has lazy list generators, we can easily include the array index in the iteration:

for ^Inf Z @array -> $index, $item { ... }

Although if infinite lists make you nervous,

for ^@array.elems Z @array -> $index, $item { ... }

will give you the same results, but the most elegant presentation is probably:

for @array.kv -> $index, $item { ... }

@array.kv returns the keys and values interleaved, where the keys of an array are the element indices, so iterating over them two at a time has the desired effect.

Hopefully this post has given you an idea of the flexibility inherent in Perl 6’s for loops and how easy they can be to use for a variety of common tasks. Before we part, I’m going to answer one final question I know somebody’s been thinking.

What, you ask, if I want to iterate over four arrays at once?

for @one Z @two Z @three Z @four -> $one, $two, $three, $four { ... }

That’s a list associative infix operator, that is. Enjoy.

21 thoughts on “Day 7: Looping for fun and profit

  1. It looks like some of your angle brackets are not properly encoded as HTML entities, because the mentions of operators frequently don’t appear correctly.

  2. Thanks, that was fun. Looking forward to learn more about pointy blocks…

    @array.map: *.say;
    => That blew my mind. What is happening here? ;)

    If there’s an easy answer: How would I use Z if I had an array of arrays and wanted to zip all arrays in it? (I guess that would be rotating a matrix or something like that.)

    1. See Moritz’s comment for the $.say business. I think we may have a post upcoming which covers the Whatever *. If we haven’t, I guess we’ll just have to stick it in the schedule somewhere.

      If you had an array of arrays and wanted to zip all the arrays in it… read the post on metaoperators to learn about the reduce metaoperator, and then do this:

      [Z] @array-of-arrays;

      At least, my afternoon brain says that’s how you do it!

      1. Excellent, I was just about to ask about a [Z] if you were using array arrays as a poor man’s class or something. It sounds VERY useful in a data table context.
        If you didn’t know a priori how many arrays were in the array array, is there some neat way to point them -> to an array of associated values? In the context of a database, let’s say you had a generic function to work with tables, and you didn’t know at scripting time how many columns you had.
        for [Z] @table -> @row { ??? }
        Does something like that work?

  3. When you use the asterisk as a term, it constructs a closure. So *+1 is a code block with one formal parameter that adds 1 to its argument.

    Likewise *.say is a closure that calls the say() method on its argument.

  4. So, the closure form of map used in the example:

    @array.map: *.say;

    Would be roughly equivalent of passing an anonymous code block:

    @array.map({ .say });

    Now, say for instance you were using a pointy block:

    @array.map: -> $a, $b { “$a $b”.say };

    Would there be a shorter way to get the same results using the Whatever form? The shortest versions I’ve thought of have been:

    @array.map: { “$^a $^b”.say };
    # or
    for @array { “$^a $^b”.say };

    BTW, I have been reading through the synopses, and haven’t come across the : closure call form yet. I definitely like it, but would like to know where to read more about it, as it seems like a useful feature.

  5. Huri: your @array.map: { “$^a $^b”.say }; works and would be my preferred way of doing this, I think. I don’t think the * is supposed to be interpolated in strings.

    The : thing isn’t closure specific, it works in general as an alternative for specifying the arguments to a method call: @array.push: "Hello", for instance.

    1. Thanks for the explanation of the : method call form. I hadn’t found it in the specs, and think it’s a pretty neat alternative method call format.

      @array.map: *.say;

      versus

      @array.map(*.say);

      Both do the same thing, but the first looks more like natural language than the second. The same goes for

      @array.map: { “$^a $^b”.say }

      versus

      @array.map({“$^a $^b”.say});

      The Perl 6 language design is very nice, and the work being done with Rakudo, Parrot and Blizkost makes me excited for the future.

  6. I see. Wow!

    I tried [Z] but got an error:
    > my @a = 1, 2, 3; my @b = 4, 5, 6; my @c = @a, @b; say ([Z] @c).fmt;
    Confused at line 1, near “@c).fmt;\n”
    in Main (file , line )

  7. bened: First, @a, @b is the list composed of all the elements of @a and all the elements of @b, rather than a list of two lists. You can get that from my @c = \@a, \@b.

    Second, [Z] apparently isn’t actually implemented yet in Rakudo.

  8. Whats going on? why is

    my @a = <a>;
    my @b = <b>;
    my @c = ;
    (@a Z @b Z @c).perl.say; # <a>

    not equal to

    (<a> Z @c).perl.say # <a>

    It looks like perl6 has different array / array ref semantics.

    1. I wasnt formatting the previous post correctly and some of the code got cut
      off. What was confusing me was the fact that:

      say (<a a a> Z <b b b> Z <c c c>).perl
      # <a b c a b c a b c>
      

      was giving a different result from:

      say (<a b a b a b> Z <c c c>).perl
      # <a c b c a c>
      

      I found (after reading Synopse 3) that this is due to the fact that the
      Z operator was list associative (not right or left). the zipping happens
      all at once, rather than two at a time. The previous examples are actually
      similar to the following (well, theres something wrong with the syntax):

      say infix:<Z>(<a a a>; <b b b>; <c c c>).perl;
      say infix:<Z>(<a b a b a b>, <c c c>).perl;
      

      So its obvoius that the zip operator knows what to do with three operands.

Leave a reply to Matthew Walton Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.