10

A line of code that I naively thought would translate fairly literally between Perl 6 and Perl 5 in fact did not, due to differences in how a post-increment variable is being handled.

This Perl 6 yields the desired result, a magic square: [[8, 1, 6], [3, 5, 7], [4, 9, 2]]

my @sq; my $n = 3; my $i = 1; my $x = $n/2; my $y = 0;
@sq[($i%$n ?? $y-- !! $y++) % $n][($i%$n ?? $x++ !! $x) % $n] = $i++ for 0..$n**2 - 1;
say join ' ', $_ for @sq;

The 'same' code in Perl 5 does not: [[8, 1, 3], [9, 5, 7], [4, 6, 2]]

$n = 3; $i = 1; $x = $n/2; $y = 0;
$sq[($i%$n ? $y-- : $y++) % $n][($i%$n ? $x++ : $x) % $n] = $i++ for 0..$n**2 - 1;
say join ' ', @$_ for @sq;

Both work correctly if the statement is split up, and all the increments and decrements are done after the assignment, e.g. in Perl 5:

for (0 .. $n**2 - 1) {
    $sq[ $y%$n ][ $x%$n ] = $i;
    $i%$n ? $y-- : $y++;
    $x++ if $i%$n;
    $i++;
}

If the return value of an increment or decrement operation is value of the variable before the operation, then Perl 5 would be seem to be in the wrong. But experimenting with the code showed that the result of the $i%$n conditions was the source of the difference, so seemingly Perl 6 may be relying upon the value of $i in a way that is not strictly guaranteed.

Thus I should have been more surprised that Perl 6 worked, than that Perl 5 didn't?

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
David Hoekman
  • 101
  • 1
  • 4
  • 1
    *"the return value of an increment or decrement operation is value of the variable before the operation*" Yes, but the value of `$i` is used on both sides of the equal sign, so the result that is put into the array may be the value before the operation, but the value of `$i` in the indexing operation could be the value after? See also: [Perl increment operator](https://stackoverflow.com/q/43546839/2173773) – Håkon Hægland May 05 '19 at 17:28
  • 2
    Is this for the obfuscated Perl contest? – Robert May 05 '19 at 17:41
  • It's from rosettacode.org, people have been know to showboat... – David Hoekman May 05 '19 at 19:00
  • @HåkonHægland you should make that an answer. the only problem here is that perl5 evaluates the right side of a scalar assignment first, while perl6 seems to do the left. – ysth May 05 '19 at 19:52

2 Answers2

9

Both Perl 5 and Perl 6 do not promise defined behavior if you modify and read the same variable within a single statement.

The Perl 6 synopses talk about sequence points in the context of parallel execution, but I believe they also apply to the read+write problem within statements.

But in general, you simply cannot rely on this in either language.

moritz
  • 12,710
  • 1
  • 41
  • 63
  • Could static analysis of code spot the potential for undefined behavior due to this? (Where "this" is the combination of multiple uses of a variable in a single statement where one of the uses is either an explicit modification of it or a method call -- because a method call might modify a value.) – raiph May 05 '19 at 21:36
  • 1
    @raiph, Obviously, it could catch some cases. There might even be a `perlcritic` rule that does just that. But it couldn't catch all cases either. For example, there's no way to know if `$_[0]` and `$_[1]` will be aliased to the same var or not in `sub { ++$_[0] + --$_[1] }`. That's why it's undefined behaviour rather than a compile-time error. – ikegami May 06 '19 at 03:38
  • 1
    Re "*Both Perl 5 and Perl 6 do not promise defined behavior if you modify and read the same variable within a single statement.*", Not quite. Perl5 actually does more or less document that the RHS of an assignment will be evaluated before the LHS. e.g. `local $x = f($x);` and `my $x = f($x);` are valid. It's a grey area because while specific cases are documented as valid, it's not clear to what extent this is true. But there is a basis in the documentation, and all versions and builds of Perl5 consistently execute the RHS of assignments separately and before their LHS. – ikegami May 06 '19 at 03:51
  • 1
    In short, you *can* rely on Perl5's behaviour here. (You shouldn't though; it's hard to read.) – ikegami May 06 '19 at 04:58
0

It seems that the difference between the Perl 5 and Perl 6 codes is that the Perl 5 code uses the updated $i earlier than Perl 6. For example, the following Perl 5 code:

my $n = 3;
my $i = 2;
my $x = 5/2;
my $y = -1;
my @sq;
$sq[ ($i % $n ? $y-- : $y++) % $n] [ ($i % $n ? $x++ : $x) % $n ] = $i++;
say "x = $x, y = $y";

prints:

x = 2.5, y = 0

whereas the same Perl 6 code:

my $n = 3;
my $i = 2;
my $x = 5/2;
my $y = -1;
my @a;
@sq[ ($i % $n ?? $y-- !! $y++) % $n][ ($i % $n ?? $x++ !! $x) % $n ] = $i++;
say "x = $x, y = $y";

prints

x = 3.5, y = -2

so the Perl 5 code uses the updated value of $i++ on the right hand side (i.e. $i = 3 to compute $i % $n on the left hand side, whereas Perl 6 uses the old value $i = 2 to compute the indices on the left hand side of the equation.

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174