http://www.perlmonks.org?node_id=248783

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

It never ceases to amaze me, how much more there is to learn in Perl. I've been using Perl for around 5 years and I tried to do something today and was suprised that I could not. Consider the following code...
#!/usr/bin/perl use strict; use warnings; my %hash = ( key => 'value'); my @array = (1..5); foreach $hash{key} (@array){ print "\$hash{key} is $hash{key}\n"; }
I would have assumed that $hash{key} would be a valid iterator variable but it's not. It dies with the message
syntax error at ../../hashodd.pl line 9, near "$hash{key"
Execution of ../../hashodd.pl aborted due to compilation erro

Obviously this is easy to get around and seeing I've never needed it before I guess it isn't a big deal, but I was suprised it wouldn't parse correctly.

The reason I attempted this BTW, was I had some validation routines and figured I would extend them to validate multiple values by wrapping the existing code in a forloop. (the inner code references the hash key). It's trivial to fix but is anyone else suprised by this?

-Lee

"To be civilized is to deny one's nature."

Replies are listed 'Best First'.
•Re: Surpised by foreach iterator limitation
by merlyn (Sage) on Apr 08, 2003 at 04:12 UTC
    There are a few places in Perl where you have to use a "simple scalar variable". The iterator binding for a foreach loop is one. The variable of an indirect filehandle read or write is another, as is the indirect object of indirect object syntax, or the expression for which you don't need braces on a dereferencing operation.

    It's a consistent notion, but not always common-sense, as evidenced by your (thankfully categorically rare) post.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      It's a consistent notion, but not always common-sense, as evidenced by your (thankfully categorically rare) post.

      I am curious about the (thankfully categorically rare) portion of your comment. The impression it leaves me with is that you think this a bad idea. While I agree it's very rare and not very useful in day to day use, it fits with the DWIM expectation which in my mind would certainly not be a bad thing.

      -Lee

      "To be civilized is to deny one's nature."
        Bah. I knew that was going to be misinterpreted, but I got lazy.

        What I'm saying is that most people don't seem to leap this direction, so we really don't see many posts wondering why it's only a simple scalar.

        I tell ya, with all the places I look, I don't see it being questioned very often.

        Maybe that's because we do a good job of describing it in the llama, so only non-llama-ites make that leap. {grin}

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

Re: Surpised by foreach iterator limitation
by graff (Chancellor) on Apr 08, 2003 at 03:19 UTC
    I see your motivation, and I get the train of thought that led to your initial assumption -- a hash element is an lvalue, and there's an intuitive sense that a loop variable is being used as an lvalue -- elements of the list are being assigned to it on each iteration.

    But that intuitive sense seems to be wrong in a subtle way. Many of us have relied on the documented behavior that results in things like:

    my @array = qw/1 2 3 4/; foreach my $elem ( @array ) { $elem *= 5; } print join " ",@array,$/; __OUTPUT__ 5 10 15 20
    As pointed out by "perldoc perlsyn": 'the "foreach" loop index variable is an implicit alias for each item in the list that you're looping over.' Another passage from perlsyn, very close afterwards, points out: '"foreach" probably won't do what you expect if VAR is a tied or other special variable. Don't do that...'

    Next, we'd have to look closely at how perl stores variables internally (The Damian's excellent book "Object Oriented Perl" has one of the nicest explanations), and try to work out how different a hash element is from a normal scalar. Well, it's late, and I won't try that just now... The point is that the loop variable is not being used as an lvalue -- it's something else with special properties.

    update: Regarding perl implementation issues, I would suppose that the compiler has a very rigid sense of for-loop syntax: the thing after "for" (or "foreach") must be a scalar; if there's a curly brace after that, it probably interprets this as the start of a block (not a hash index), and reports the syntax error because there's no list being provided for the iteration. Of course, the error is reported if you're using an array element (square-brackets) as well.

      It is not different in such a way as to prevent a suitable implementation from functioning. Yes, it would require a little bit of magic, but no more than the magic already provided by local(). Where there is a will, ... This is Perl people... :-)

      I just assumed it would hv_fetch() with lval set. I haven't revisited the source yet but I suspect it's a grammer issue.

      -Lee

      "To be civilized is to deny one's nature."
Re: Surpised by foreach iterator limitation
by MarkM (Curate) on Apr 08, 2003 at 03:09 UTC

    Before around perl 5.004, even "my" was not recognized as a means to qualify the iterator variable as having a new lexical scope.

    Most things Perl are defined by their actual implementation, and formed via a relaxed but somewhat controlled sort of evolution. In this case, no, I was not surprised. Most likely, because years ago, I tried it, and realized that it did not work... Thankfully, some good people have worked on making syntax such as "EXPR foreach 1 .. 10" work as I had originally expected. So there is hope yet, if you mention this 'inconsistency' to the right person.

      "Before around perl 5.004, even "my" was not recognized as a means..."
      <confession>Didn't know that. OTOH, I don't think I was using my or strict or warnings back then.</confession>

      -Lee

      "To be civilized is to deny one's nature."
Re: Surpised by foreach iterator limitation
by demerphq (Chancellor) on Apr 08, 2003 at 20:47 UTC

    Oddities in the for notation abound... The following is valid perl

    foreach my $foo qw(bar baz) { print $foo,$/ }

    :-)


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...

    • Update:  
    *Sigh* apparently people have missed the point here. The qw is special cased so you dont have to write

    foreach my $foo (qw(bar baz)) { }

    Foo on you people who thought I was being sarcastic!

      ++demerphq!

      I just read your update and now I can see what you mean. I've been using that syntax for some time and never even noticed the missing parentheses. So I set out to see if there are any other operators that would work in this situation without outer parentheses. So far I haven't found anything besides the qw// operator.

      foreach is expecting an operator that returns a natural list, generally the list operator: (list). qw// works because it is an operator that returns a list at compile time!

      Am I missing any other operators that behave the same way?

      What is so odd about that?

      foreach loops over a normal list value. qw returns a normal list. The last line of a block doesn't require a semicolon. It looks quite normal to me.

      Unless of course you mean... foo bar baz. :-)

      Odd indeed.

      -Lee

      "To be civilized is to deny one's nature."
Re: Surpised by foreach iterator limitation
by pg (Canon) on Apr 08, 2003 at 03:00 UTC
    Just to recap my main point thru out this subthread: (many points here is not directly against the original post, but against the whole subthread. Many of those things I disagree with is even not part of shotgunefx's original purpose, but MarkM's extension. I disagree with MarkM's points, but not him as a fellow monk ;-)

    $hash{key} is the valid syntax to name a hash element, which happened to be a scalar (which could be array ref, hash ref ...), does not mean $hash{key} is a valid variable name for a scalar.

    There is something subtle here, and we are looking at two different perspectives.

    maybe a better way to explain this subtle difference is to look at some other computer language, for example c:

    You can define a collective int a[200];, and ref to its 100th element by saying a[99], but a[99] is not a valid variable name. Otherwise, how can you recognize whether a[99] is a single variable or an element of a collective data structure?

    One may say that this is Perl, not c. Well, when you look at the foundation, they have more similarities than differences. Whatever language it is, it requires the syntax can be consistently parsed/recognized.

    Some people may argue that there are lots of "bad" syntax in Perl, what a big deal to have one more? Again, here is something subtle. Perl might have many "bad" syntax from a conventional language point of view, but all those "bad" syntax can be consistently parsed according to context. I bet nobody dare to have "bad" syntax that cannot be consistently parsed.

    (the original content of this original reply is based on misunderstanding of what shotgunefx means. He actually means something much more subtle.)I don't call that a limitation ;-). To iterate thru a hash, you would do something like:
    %hash = (a=>1, b=> 2, c=>3); foreach (keys %hash) { print "\$hash{$_} = $hash{$_}\n"; }
    What makes this conventional approach not work for you?
      Umm.. I wasn't attempting to iterate through a hash. I was trying to extend a validation function that takes a hash as it's argument to accept multiple values in the laziest way possible. $hash{key} is a scalar variable and can be localized, so I assumed it could be used any place a scalar could which was the point of the post.

      -Lee

      "To be civilized is to deny one's nature."
        Okay, now I see, however, $hash{key} is even not a valid name for a scalar variable.br>
        Be careful about this subtle difference: $hash{key} is the valid syntax to point to a hash element, which is a scalar, but it is not the valid syntax to name a scalar.

      I certainly wasn't suggesting that $hash{key} be a valid scalar name. I simply and mistakenly thought that any lvalue would work in the iterator position.

      -Lee

      "To be civilized is to deny one's nature."
        Seriously, what you suggested here makes sense to me, but I think it is just the syntax, and the parser requires a valid variable name here, (or if it is missing, $_ is assumed).
Re: Surpised by foreach iterator limitation
by diotalevi (Canon) on Apr 08, 2003 at 03:43 UTC

    I don't even understand what that snippet of code means. Do you mean that the $hash{ 'key' } variable should be aliased to each of the loop's variables? Or is 'key' supposed to be special somehow in that the iterated value forms the key to the hash? I just can't see how this construction makes sense. Explain it to me - what on earth does it mean?

      I thought the last paragraph of my post made it clear but perhaps not. I had an involved sub (actually subroutine generator) for validating data. It takes as it's argument a hash. The sub references this in may places. I wanted to refactor it to take multiple arguments for each call. Currently it expects one piece of data. I figured the easiest way and least likely way to intruduce bugs would be to wrap the code in a loop. For example
      sub example { # Expects argument data that is a single value my %p = (@_); # return if $p{required} && !$p{data}; Do stuff that references $p{data}... return 1; # OK } my $value = 10; if ( example(data=>$value, minval=>5, maxval=>10){ print "$value is OK!\n"; }else{ die "$value is naughty!"; } Now let's say I want to extended it to take multiple values I thought the easiest way would be to do this. sub example { my %p = (@_); # my @data = UNIVERSAL::isa($p{data},'ARRAY') ? @{$p{data}} : ($p{ +data}); foreach $p{data} (@data){ return if $p{required} && !$p{data}; Do stuff that references $p{data}... } return 1; # OK }
      It did not work.

      This can be solved in a million simple ways such as using a lexical for the iterator and simply$hash{key} = $iterator_variable as the first statement in the loop.) and I am not looking for a fix. I was simply suprised the Perl could not parse this.

      -Lee

      "To be civilized is to deny one's nature."
        Explain to me why this wouldn't work:
        sub exampl { my %p = @_; my @data = UNIVESAL::isa($p{data}, 'ARRAY') ? @{$p{data}} : ( $p{data} ); foreach my $datum (@data) { return if $p{required} && !$datum; # Do stuff that references $datum } return 1; # Everything is OK }
        The point here is that you're stuck in the mode of "I have to reference things the same way". Much better is to say "I have an array of stuff. Let's work with that array."

        Remember - You used to call it $p{data} because it was a member of that hash. Now, it's not. So, don't call it that. Embrace the refactoring goodness.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: Surpised by foreach iterator limitation
by gmpassos (Priest) on Apr 08, 2003 at 19:34 UTC
    Take a look in this node "Foreach & scalar ref: break of way that it is suposed to work!". I put it there to make a discussion about it. You can see a code of how to write/read to a scalar variable and affect directly the value of a key too (without Tie). And why what you want to do doesn't work, and shouldn't work...

    Graciliano M. P.
    "The creativity is the expression of the liberty".