calin has asked for the wisdom of the Perl Monks concerning the following question:

I call a flattened hash the list that is produced when a %-literal is used in a list context ; it can be thought as a "print representation" (LISP-speak) of a hash object. It is a list consisting of hash keys in odd position (0, 2, ...) with corresponding hash values in subsequent even position (1, 3, ...) (0 is first position in Perl lists, therefore odd :) ). It's used when copying hashes to/from other hashes or arrays or passed to arbitrary list contexts; also a hash can be initialized from an arbitrarily generated list, which is interpreted with flattened hash convention, as in the most common and idiomatic hash initialization construct: %h = (one => 1, two => 2);. Most monks are already familiar with these issues, so I won't press on.

While meditating on these flattened hashes, a question occurred to me: do they preserve lvalueness of value (even) positions when used in aliasing contexts (subroutine call and foreach)? A quick experiment revealed that they do:

$ perl my %h = 'a'..'f'; sub modify { for (my $i = 1 ; $i < @_ ; $i+=2) { $_[$i] .= '_modified'; } } modify(%h); print "$_ => $h{$_}\n" for sort keys %h; ^D a => b_modified c => d_modified e => f_modified $ perl my %h = 'a'..'f'; # assignment to key (odd) positions has no side effects $_ .= '_modified' for %h; print "$_ => $h{$_}\n" for sort keys %h; ^D [the same output]

Now I'm not advocating this as a technique for hash modification in called subroutines etc.! The usual method of call-by-reference is much cleaner and probably(?) more efficient. I only have a few questions for monks who are more intimate with Perl internals/development history:

  1. Is this intentional / documented behaviour or just incidental?
  2. If intentional, what's the reason? Historical? Incidental behaviour become standard with time? Implemented just to stay consistent? ...
  3. Do you think it conforms to the Principle of Least Surprise (I can't make up my mind on it)

LVALUEness of values

My experiments revealed that values is also lvaluable (and this is documented). With aliasing, it's useful for key-agnostic bulk value modifications.

Replies are listed 'Best First'.
Re: LVALUEness in flattened hashes
by jeffa (Bishop) on Jan 09, 2004 at 21:25 UTC
    How about a couple more tests?
    use Data::Dumper; my %h = 'a'..'f'; $_ .= '_mod' for %h; print Dumper \%h;
    Here we try to modify every element in the 'hash', and the result show that only values can be changed, not the keys. But doesn't this make sense? Have you ever found yourself trying to change hash keys? You can't, you have to make a new key instead:
    $h{a_mod} = delete $h{a};
    Now, let's take chromatic's advice and try this with an array:
    my @h = 'a'..'f'; $_ .= '_mod' for @h; %h = @h; print Dumper \%h;
    Because at the time we changed the 'keys', they weren't yet keys. ;)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: LVALUEness in flattened hashes (efficient)
by tye (Sage) on Jan 09, 2004 at 21:19 UTC

    To do otherwise would be inefficient. We1 make copies of the keys because we have to (since hash keys aren't even scalars, they are simple strings).

    Most of the time, whether we put aliases2 of values into the flattened list doesn't matter so why go to extra effort to make copies of each value (whether they be read-only copies or lvalue2 copies but certainly not LVALUE2 copies3).

    It certainly would surprise me if flattened hashes didn't contain aliases, but that is mostly because of what I wrote above, which probably disqualifies me for that test.

    I was certainly surprised that values didn't return aliases and that I had to resort to @hash{keys %hash} instead of values %hash until recently.

    And it is consistant (as much as is possible) with flattened arrays. But the values are inconsistant with the keys in the same regard.

    So I think the strongest argument is efficiency (which may have been an "accident" of an efficient implementation or may have been a designed efficiency -- you'll probably have to find the author to know for sure).

                    - tye

    1 Not that I'm taking any credit for the implementation.

    2 I'd replace most of your uses of "LVALUE" with "alias". lvalues can be assigned to (end of definition). LVALUEs are magic lvalues that copy any changes made to them to some "original" (or part of it).

    :lvalue subroutines may muddy the water by returning aliases (I'd test but that feature appears to be quite broken in my moderately old versions of Perl), but their name indicates the function can be used as an lvalue. Common sense says that such is only useful if the changes get propogated back. Whether they get propogated back via the use of aliases or LVALUEs is an implementation detail.

    So I wouldn't use "lvalue" (nor "LVALUE") to mean "assigments propogate back" since the former doesn't always imply that and the latter is a specific special case of it. (Which leaves us two terms for the two cases and no umbrella term, but I don't mind.)

    3 Though recent changes make me think that we might end up with LVALUE values in the case of flattened tied hashes. Oh my!

Re: LVALUEness in flattened hashes
by chromatic (Archbishop) on Jan 09, 2004 at 20:24 UTC

    I think you've rather instead discovered the aliasing properties of @_. Try it with an array.

Re: LVALUEness in flattened hashes
by antirice (Priest) on Jan 09, 2004 at 19:35 UTC
    1. This certainly seems like the expected behavior (didn't look for documentation). The aliasing tricks happen with arrays and scalars as well. Why shouldn't it work with hashes in a list context? As for the keys not changing, I wouldn't expect them to since this would mean the key would need to be rehashed and possibly moved to a different bucket. It's easier to just make it unmodifiable.
    2. Wouldn't you expect the same behavior if you just used $h{'a'} or @h{qw(a c e)}?
    3. It'd surprise the hell out of me if it didn't behave this way.

    antirice    
    The first rule of Perl club is - use Perl
    The
    ith rule of Perl club is - follow rule i - 1 for i > 1

      Wouldn't you expect the same behavior if you just used $h{'a'} or @h{qw(a c e)}?

      Yes, I would expect that (I thought about this before posting). Assigning to $h{x} is ... the way to set a hash element. Also assignments to hash slices are common and widespread. But, could you show me any published code (CPAN module, etc., even obfu will do) that uses the lvalueness properties of a flattened hash? Would you rely on this in your own code?

      It'd surprise the hell out of me if it didn't behave this way.

      Flattened hashes were read-only lists created on the spot, on my mental map, until I found otherwise. It makes sense to me - and it doesn't. It's somewhere on the borderline. That's why I'm seeking wisdom.

      Also, please note, the point of my OP is not about arguing that Perl should do this or that -- all expressed opinions are IMHO. I'm just seeking an authoritative answer.

      To conclude, your opinion falls into for the sake of consistency, right?

        ... could you show me any published code (CPAN module, etc., even obfu will do) that uses the lvalueness properties of a flattened hash?

        Unfortunately, no code that uses the lvalueness properties of a flattened hash comes to mind. I think this is due to the fact that values and slices provide both lvalues and a list that is at longest half the length of just %h.

        Would you rely on this in your own code?

        In all truth, no. This is mainlydue to the fact that I would use values or slices instead.

        Also, please note, the point of my OP is not about arguing that Perl should do this or that

        If I came across as argumentative, I apologize.

        To conclude, your opinion falls into for the sake of consistency, right?

        Somewhere in there. I think it's behaving somewhat like a list of scalars with every "odd" one (as you put it) an unmodifiable value that doesn't die when you attempt to modify it. If that qualifies as "for consistency" then yes. I cannot think of a reason why you wouldn't offer it when the facilities for it are available. Furthermore, aliasing the values is less expensive than copying to a read-only list.

        antirice    
        The first rule of Perl club is - use Perl
        The
        ith rule of Perl club is - follow rule i - 1 for i > 1

Re: LVALUEness in flattened hashes
by duff (Parson) on Jan 09, 2004 at 22:18 UTC

    I've read your post and the replies to it so far and I confess that I haven't a clue as to what you're talking about (though others appear to). You seem to be amazed at two separate perl constructs:

    1. the aliasing properties of @_ in a subroutine, and
    2. the aliasing properties of $_ in a for loop

    Either way you look at it, @_ will contain aliases for each element of %hash in foo(%hash) and $_ (or whatever you've named your loop variable) will alias each element of %hash in for (%hash) { ... }. It has nothing to do with the fact that you've stuck a hash in those places. It would do exactly the same thing if you stuck an array there instead (other than the keys of a hash are immutable while there is no such restriction on an array). Or if you'd stuck any other list of scalars in there instead. The only wrinkly bit is that the scalars that correspond to hash keys are immutable.

    What it sounds like you mean by "lvalueness in flattened hash" is that (%hash)[$_] .= "_mod" for 0..(keys %hash) should work just like $_.="_mod" for %hash. But that obviously won't work.