Earlier today I was playing around with the code in Perl Destroys Interview Question. Curiosity led to experimentation, and soon I found myself putting a map fed by split inside the key list of a hash slice. Eventually I stumbled across something that I found kind of interesting. It was one of those "What happens if I pull this lever?" moments.

(I still feel shy about referring to myself as one of "us monks" when the other monks in "us" happen to be monks of prominant standing within the Monastery and Perl community as a whole.) Anyway, a few of us monks ;) kicked some ideas around in the CB, and that helped me to kind of refine my thoughts on the following issues...

The remainder of this meditation deals with what I believe is pretty much undefined behavior resulting from context mismatched code. For all of the following examples, assume that strictures and warnings are turned on.

Start by considering the following:

my $a; $a = ( 1, 2, 3 );

The actual (and defined) behavior of using the comma operator in scalar context, according to perlop is to assign the rightmost value to $a. We're all used to this by now (hopefully).

Now consider this code:

my ( $a, $b, $c ) = 1;

perlop doesn't seem to comment on what happens when you assign a single scalar value to a list, but the actual behavior is for $a (the first element of the lvalue list) gets set to 1. The other elements remain undeffed.

If you do it this way:

my ( $a, $b, $c ); ($a, $b, $c) = (1, 2, 3); ($a, $b, $c) = 1;

You can see that the assignment is actually pretty much doing as expected; assigning 1 to the first list element, and assigning undef to the rest of the elements.

Now try something bizzarre:

my ( $a, $b, $c ) = (1, 1, 1); ( $a, $b, $c ) += 1;

What do you get? Two warnings are generated (the same warning, twice): Useless use of private variable in void context at.... And the surprise now is that the last element is what got incremented. $c is now 2. The others are still 1.

I'm actually happy to see a warning here. But I'm surprised to find that the third element is now what got incremented. Undefined behavior, perhaps. Good thing there's a warning!

For what its worth, try this code too:

my ( $a, $b, $c ) = ( 1, 1, 1 ); ($a, $b, $c)++;

Now you get the same two warnings, plus a compilation error: Too many arguments for postincrement (++) at.... Bravo, I wanted an error here; there simply can't be any basis for DWIM in this case.

Ok, hold onto your hats, let's introduce hash slices (array slices behave similarly, but I was just playing with hashes today, that's all)...

my %hash = ('a'=>1, 'b'=>1, 'c'=>1); @hash{'a'..'c'} = 2;

This has the effect of assigning 2 to $hash{'c'}. ...the last first element in the slice. Remember, ($a, $b, $c) = 2; assigns 2 to the first element. This represents, what I consider, inconsistant behavior. By the way, just as expected, the elements remaining elements were all assigned undef as their value. Update: This case was a false alarm.

Now try this:

my %hash = ( 'a'=>1, 'b'=>1, 'c'=>1 ); @hash{'a'..'c'}+=1;

Here, we add-assign the last element of the hash slice, $hash{'c'}, and the other two remain 1. So += is consistant inconsistant with =.

And here's the last test:

my %hash = ( 'a'=>1, 'b'=>1, 'c'=>1 ); @hash{'a'..'c'}++;

Here, we also increment the last item of the hash slice; 'c' becomes 2.

My points to all this:

I don't know what to think of it all. Most of it seems to be completely undocumented. My biggest issues are: (1) inconsistancy in outcome, and (2) inconsistancy in warnings/errors.

Update: My testing was done with Active State Perl v5.8.1 on a WinXP system with use strict; and use warnings; set. I am seeing comments in CB that some are seeing differing results.

Thanks bart and antirice for double-checking my work. All those tests upon tests, I misread one of them. My points on all other issues aside from the assignment operator (=) seem to remain valid still.

I think that at minimum, slices, and pure lists should behave the same with respect to which elements get affected, and with respect to what warnings are issued when using += and ++.


Dave

Replies are listed 'Best First'.
Re: Fringe case slice behavior
by Abigail-II (Bishop) on Jan 13, 2004 at 10:12 UTC
    Actually, the odd operator is =, not +=. Most operators will dictate the context of their operands, and so does +=: both operands are in scalar context. But = is special cased, there the operands could be either in list or scalar context - and it's the left operand that decides.

    The following program might be of interest:

    #!/usr/bin/perl use strict; use warnings; my ($a, @a) = (""); sub foo : lvalue { print wantarray ? "LIST: " : "SCALAR: "; wantarray ? @a : $a } foo () = 1; print "\$a = $a; \@a = [@a]\n"; (foo ()) = 2; print "\$a = $a; \@a = [@a]\n"; foo () += 3; print "\$a = $a; \@a = [@a]\n"; (foo ()) += 4; print "\$a = $a; \@a = [@a]\n"; __END__ SCALAR: $a = 1; @a = [] LIST: $a = 1; @a = [2] SCALAR: $a = 4; @a = [2] SCALAR: $a = 8; @a = [2]

    Abigail

Re: Fringe case slice behavior
by bart (Canon) on Jan 13, 2004 at 08:23 UTC
    my %hash = ('a'=>1, 'b'=>1, 'c'=>1); @hash{'a'..'c'} = 2;
    This has the effect of assigning 2 to $hash{'c'}. ...the last element in the slice.
    That would be extremely inconsistent indeed, as @hash{'a'..'c'} is supposed to be equivalent to ($hash{'a'}, $hash{'b'}, $hash{'c'}). And it is. What I get for \%hash, with Data::dumper, for both, is:
    $VAR1 = { 'a' => 2, 'b' => undef, 'c' => undef };
    both with 5.6.1 and with 5.8.0 (though the order might vary). <sigh-of-relief/>. So it is consistent.

    Code used for testing:

    my %hash = ('a'=>1, 'b'=>1, 'c'=>1); @hash{'a'..'c'} = 2; use Data::Dumper; print Dumper \%hash; %hash = ('a'=>1, 'b'=>1, 'c'=>1); ($hash{'a'}, $hash{'b'}, $hash{'c'}) = 2; print Dumper \%hash;
Re: Fringe case slice behavior
by TimToady (Parson) on Jan 13, 2004 at 18:21 UTC
    Some of these weirdnesses will go away in Perl 6. It is still true that = will take its context from the left side, while += will enforce scalar context on both sides. What will change, however, is that any list-producing operator that returns its last value in scalar context in Perl 5 will instead produce a list reference in Perl 6.

    That means there's no C-style comma operator in Perl 6. If you want the last value of a list, subscript it with [-1]. This also means that a slice in scalar context will not return the last value as it does in Perl 5. If you try that in Perl 6, you'll get a list reference, which will fail when you try to apply a scalar operator like += to it. (Perl 6 has a way to apply scalar operators to lists, but you have to be explicit.)

      I remember seeing some discussion about replacing the c-style comma operator with a keyword that achieved a similar effect. Various option where discussed -- also & then were muted I think.

      Was any conclusion reached as a) whether there would be such a keyword? b) What it is likely to be?


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      Timing (and a little luck) are everything!

        Was any conclusion reached as a) whether there would be such a keyword?
        Not by me. :-)
        b) What it is likely to be?
        I suspect a pop should be made to do the expected thing, in which case you have your keyword (or at least, method name). But as I said, a [-1] subscript will certainly work. It may well become the idiom of choice for when you really do want to select the last value.

        But note that many uses of the C-style comma operator are in fact in void context, in which case you don't much care whether it returns the last value or a reference to the list, since it's just going to throw it away anyway. So you can just use comma in those cases.

        I cannot remember any discussion to replace the C-style comma in the 7 years that I've read p5p. I don't think such a proposal would make any chance, for several reasons: there is already a synonym for the comma: =>; it doesn't save anything, instead of a single keystroke, you'd have to use at least 4, but often 5. And it has the potential to break existing code.

        The comma is a well-known construct - especially amongst the people on p5p, as they tend to know C quite well.

        Abigail

Sourcediving Challenge: Fringe case slice behavior
by Zaxo (Archbishop) on Jan 13, 2004 at 08:14 UTC

    I think you are prying about the weedy backlots of perl syntax. Afaik, the is nothing in the perl grammar that says this should happen.

    [Open Challenge]
    Your mission, should you choose to accept it, Friar Monk, is go sourcediving into toke.c with the purpose of discovering what cosmic accident produces this behavior.

    After Compline,
    Zaxo

Re: Fringe case slice behavior
by ysth (Canon) on Jan 13, 2004 at 16:09 UTC
    All seems pretty normal to me, except for the warning on ++ but not on +=. I don't see where you show slices and pure lists working differently.

    I think most of your trouble is with context. += and ++ give their operands scalar context, = only gives context to the right operand, based on what is on the left.

Re: Fringe case slice behavior
by tilly (Archbishop) on Jan 13, 2004 at 19:25 UTC
    Um, most of this is documented behaviour. For instance if you assign a scalar to a list, you actually assign a list of length 1 to a list, and extra entries get undef.

    Secondly you're misrepresenting the defined behaviour of comma in scalar context. It isn't that comma acts that way, it is that lists act that way and comma normally delimits lists. Unless precedence gets in the way as in @foo = 1, 2; which is parsed (@foo = 1), $bar = 2;

    Of course when you later look at +=, the same list behaviour shows up on the left hand side, resulting in the last element in the list being incremented.

    The hash slice behaviour is all, of course, undefined since the order of keys is arbitrary (even more so in recent Perls).

    The exact warnings that you get from various situations are, however, more likely to change from version to version.

    UPDATE: Oops, ysth is (of course) correct. My bad.

      The hash slice behaviour is all, of course, undefined since the order of keys is arbitrary (even more so in recent Perls).
      Slices are always in the order in which you specify the keys.
      Secondly you're misrepresenting the defined behaviour of comma in scalar context. It isn't that comma acts that way, it is that lists act that way and comma normally delimits lists

      I'm inclined to respectfully disagree here. According to perlop, "Binary "," is the comma operator. In scalar context it evaluates its left argument, throws that value away, then evaluates its right argument and returns that value. This is just like C's comma operator. " I feel like this is a better way to think about it, since it's consistent with the doctrine of "there's no such thing as a list in scalar context."