Why is the Perl 6 loop variable declaration in the outer scope?

advertisements

In Perl 6, a variable declared in the for-style loop is in the outer scope. This works and is documented.

loop ( my $n = 0; $n < 3; $n++ ) {
    put $n;
    }

say "Outside: $n";

The $n is visible outside of the block:

0
1
2
Outside: 3

Is there a motivating trick here that makes this different from what one would expect from Perl 6's ancestors? I didn't see a mention of this in the design docs (but try searching "loop" sometime). I couldn't come up with an example where this would make things easier.

In Perl 5, the same thing is a strict error:

use v5.10;
use strict;

for ( my $n = 0; $n < 3; $n++ ) {
    put $n;
    }

say "Outside: $n";  # Nope!

And, in C (those that let you do this), it's a similar error:

#include <stdio.h>

int main () {

   for( int a = 10; a < 20; a = a + 1 ){
      printf("value of a: %d\n", a);
   }

   printf("value of a: %d\n", a);  /* Nope! */

   return 0;
}

As usual with my questions, I'm not interested in workarounds. I know how to do those.


As I noted in the comments, Synopsis 4 pushes the implementation to declare lexicals only inside the block where they will appear.

The $n appears before the { so it's not "inside" the block.

However, what about pointy blocks?

-> $a { put "a is $a" }

And, subroutine signatures?

sub ( $a ) { put "a is $a" }

These variables are first typed before the {.

I don't particularly care about this but if I have to explain this break with tradition by saying that you have to declare lexical variables in the blocks where you will use them, someone can point out these cases.


You can think of it as a block is what creates a lexical scope.

if (1) {
  my $a = 1;
}
$a;  # error

if (my $a = 1) {
}
$a;  # no error

loop (my $a = 1; $a == 1; ++$a) {
}
$a;  # no error

It's just that other C-like languages special case the (C style) for loop, and the Perl 6 design is to have as few special cases as possible.

So if something is very useful in one place, it should be designed so that it is useable everywhere.


For example -1 indexing in arrays.
Rather than just having @a[*-1] as special syntax constrained to array indexing, *-1 is just a way to create a lambda/closure that you can use anywhere you want a simple lambda.

Another example is pointy blocks being more than just a way to declare the iteration variable(s) in a for loop.

for @a -> $value {…}

They are usable with any similar construct.

if $a.some-resource-expensive-op -> $result {…}

Instead of

{
  # note that $result isn't read-only like it is above
  my $result = $a.some-resource-expensive-op;
  if $result {…}
}

You can even use it as just another syntax to create a lambda.

@a.sort: -> $value {…}


In order to make the language simpler to reason about, special cases need to pull their own weight, and having only the arguments of the loop construct, and no other construct, be part of the block just doesn't.
If it were generally useful then it would be different.

Also the loop construct is one of the few features that doesn't really fit with the general design aesthetic of Perl 6. I think it might be easier to argue for its deprecation and removal than for it to be more special cased.
(no one is arguing for it to be removed)