Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Modifying $_ in foreach (%hash)

by dse (Novice)
on May 22, 2018 at 04:39 UTC ( [id://1215016]=perlquestion: print w/replies, xml ) Need Help??

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

Is the behavior defined when modifying $_ when doing a foreach directly on a %hash variable, i.e., without using keys or values, to iterate through the hash keys *and* values? If it's specified somewhere in the man pages it's not clear to me so pointing me to where would be fine.

In other words, is it defined that the example code below:

#!/usr/bin/env perl
use warnings;
use strict;

my @array;
my %hash;

%hash = (1, 2, 3, 4);
@array = %hash;

print("<<<< @array\n");

foreach (%hash) {
    print("<< $_\n");
    $_ *= 11;
    print(">> $_\n");
}

@array = %hash;

print(">>>> @array\n");

outputs the following (last line is key)?

<<<< 1 2 3 4
<< 1
>> 11
<< 2
>> 22
<< 3
>> 33
<< 4
>> 44
>>>> 1 22 3 44

(This is Perl v5.26.1.)

My project is to implement a version of chomp that tries to modify its arguments as close to what chomp is capable of though I realize you cannot specify a prototype on a subroutine that handles it exactly the same.

Basically I'm trying to determine if it is defined what happens when a %hash argument is passed to a sub (@) prototyped subroutine.

Replies are listed 'Best First'.
Re: Modifying $_ in foreach (%hash) (updated)
by haukex (Archbishop) on May 22, 2018 at 06:04 UTC

    <update> The same question, with several good answers: Modifying hash keys via aliasing </update>

    The best I can do at the moment is this quote from perlref - although your question is really about aliases and not "hard" references, so I may be off here.

    As a special case, \(@foo) returns a list of references to the contents of @foo, not a reference to @foo itself. Likewise for %foo, except that the key references are to copies (since the keys are just strings rather than full-fledged scalars).

    Also, note that the behavior when passed a hash (which is kind of unusual BTW, why do you want to do that?) is consistent between chomp and a custom sub.

    use warnings; use strict; use Data::Dump; sub antichomp (@) { for (@_) { $_ .= $/; } } my %hash = (x=>'Hello',y=>'World!'); antichomp(%hash); dd \%hash; %hash = ("x\n"=>"Hello\n","y\n"=>"World!\n"); chomp(%hash); dd \%hash; __END__ { x => "Hello\n", y => "World!\n" } { "x\n" => "Hello", "y\n" => "World!" }

    prototype("CORE::chomp") does return undef, indicating that there isn't an exact equivalent, but I do think that you can get pretty close.

    As for all the other behaviors, at least those are defined:

    Hashes in list context return a list of their key/value pairs. Excerpts from perldata:

    ... hashes included as parts of other lists (including parameters lists and return lists from functions) always flatten out into key/value pairs.

    LISTs do automatic interpolation of sublists. That is, when a LIST is evaluated, each element of the list is evaluated in list context, and the resulting list value is interpolated into LIST just as if each individual element were a member of LIST. Thus arrays and hashes lose their identity in a LIST--the list

    (@foo,@bar,&SomeSub,%glarch)

    contains all the elements of @foo followed by all the elements of @bar, followed by all the elements returned by the subroutine named SomeSub called in list context, followed by the key/value pairs of %glarch.

    The behavior of foreach loops in regards to the iterator variable is defined in perlsyn:

    The foreach loop iterates over a normal list value and sets the scalar variable VAR to be each element of the list in turn. ... the variable is implicitly local to the loop and regains its former value upon exiting the loop. ... This implicit localization occurs only in a foreach loop. ... If VAR is omitted, $_ is set to each value.

    If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. ... In other words, the foreach loop index variable is an implicit alias for each item in the list that you're looping over.

    Arguments to subroutines are also aliases to the original values, as long as you access the elements of @_ directly, without first doing the typical assignment like my ($x,$y) = @_; or my $x = shift;. From perlsub:

    Any arguments passed in show up in the array @_. ... The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not updatable). ... Assigning to the whole array @_ removes that aliasing, and does not update any arguments.

    Prototypes are discussed in perlsyn, and it's easy enough to test and see the same behavior of hashes flattening into a list of unordered key/value pairs here too:

    use warnings; use strict; use Data::Dump; sub foo (@) { dd \@_ } my %hash = (x=>'Hello',y=>'World!'); foo(%hash); __END__ ["y", "World!", "x", "Hello"]
Re: Modifying $_ in foreach (%hash)
by choroba (Cardinal) on May 22, 2018 at 05:40 UTC
    I can't find an exact quote, but for me the behaviour isn't surprising. See values for similar behaviour: values can be modified, but keys can't. To modify a hash key, you need to delete the old one and add a new one.
    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Modifying $_ in foreach (%hash)
by davido (Cardinal) on May 22, 2018 at 15:17 UTC

    Observe this experiment, which is relevant to your discussion of what happens when a hash argument is passed to a subroutine:

    sub increment { $_++ foreach @_; } my %hash = qw(a 1 b 2 c 3); increment(%hash); print "$_ => $hash{$_}\n" for sort keys %hash;

    The output will be:

    a => 2 b => 3 c => 4

    As others have pointed out, hash keys cannot be modified. And in our increment() subroutine, $_ is an alias to each value in @_, which aliases each key and value in %hash.

    What is most likely to cause surprise to the coder is that had we passed the following to our increment() sub, we would get an exception thrown:

    increment(qw(a 1 b 2 c 3)); ... Modification of a read-only value attempted at...

    The surprising behavior in the case of passing the hash is that Perl's error handling doesn't report the attempt to modify a hash key. I can't really say whether it should report such a thing -- it might have been decided intentionally that was not desirable. Or it could have just not been noticed that there's a path to attempt to modify hash keys through aliasing.

    If you look at the operations that must be provided when tieing a hash, you'll not find a hash key modification operation, though. Looking at Tie::Hash we see that the hash operations are TIEHASH, STORE, FETCH, FIRSTKEY, NEXTKEY, EXISTS, DELETE, CLEAR, and SCALAR. In perltie we see additional methods mentioned: UNTIE, and DESTROY. But there's no method such as RENAME or MODKEY.

    Keep in mind that conceptually modifying a hash key makes about as much sense as modifying an array index. You cannot change an array index, you can only change which index a value is stored in. While hashes are different than arrays in that they have no inherent order, and that their indices are strings, they are similar in that their indices are immutable. Additionally, even were it possible to modify a hash key, the result would be a new hashing outcome, just as changing a character in a document that is passed through a SHA1 hashing algorithm should cause the resulting SHA1 to change. If the hashing outcome changes, the bucket location changes, so modification of a hash key would only work if the data were moved to the new location.


    Dave

Re: Modifying $_ in foreach (%hash)
by BillKSmith (Monsignor) on May 22, 2018 at 16:01 UTC
    I believe that your first question is answered in the third paragraph of "foreach loops" section of perlsyn.
    If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail...
    Note that hash keys are not lvalues.
    Bill
Re: Modifying $_ in foreach (%hash)
by ikegami (Patriarch) on May 22, 2018 at 19:25 UTC

    Answered here. It talks about a sub call and @_, but the same applies to a foreach loop and the loop var ($_).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1215016]
Approved by Athanasius
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (6)
As of 2024-04-25 11:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found