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

Not sure how to title this, since I don't know where the issue is. How come this works:

my @ary; while ( <DATA> ) { chomp; push @ary, $_ and next if $_> 5; print; } __DATA__ 3 7 9 1

and not this:

my %hash; while ( <DATA> ) { chomp; $hash{$_}++ and next if $_ > 5; print; } __DATA__ 3 7 9 1

The latter outputs 3791 rather than 31. I know how to get around this; for example I can change the key line to:

  $hash{$_}++, next if $_ > 5;

I just wonder what the issue is? (BTW I use strict and warnings but get no complaints.)

Thanks, once again, in advance.

dave

Replies are listed 'Best First'.
Re: 'and next' question
by sauoq (Abbot) on Oct 26, 2003 at 19:27 UTC

    Consider these examples:

    $ perl -le 'print $h++' 0 $ perl -le '0 and print "this will not be printed"' $
    You are post-incrementing your variable. It isn't 'true' until after the and is evaluated. Since the lefthand side of the and is false, the righthand side is never evaluated. This is called "short-circuiting" and it is often very useful.

    As you discovered, you can use a comma to get what you want; you could also change your post-increment to a pre-increment. But I don't think either of those options is the best way to do it. There's really no value in being overly idiomatic. The straight-forward approach is probably best:

    while ( <DATA>) { chomp; if ($_ > 5) { $hash{ $_ }++; next; } print; }

    -sauoq
    "My two cents aren't worth a dime.";
    
      "You are post-incrementing your variable. It isn't 'true' until after the and is evaluated."

      I am a little bit concerned that this statement could be easily misunderstood (I strongly believe that you mean the right thing, and I don't intend to second guess, but I want make this a little bit more clear and precise. I know that I am a little bit picky here ;-).

      If you try this code:

      my $a = 1; $a++ and print $a;

      You will get 2, which shows that right after "$a++" is evaluated, and before the entire second line finished, ++ already happened.

      From a pure language view, that "It" in your sentence could stand for the phrase "your variable", which appeared earlier. In the case of the original post, that variable is already true before that "and" is evaluated, but it is false before the "++" happened. However "++" happens before "and".

        my $a = 1; $a++ and print $a;
        You will get 2, which shows that right after "$a++" is evaluated, and before the entire second line finished, ++ already happened.

        Yes, but in your example no short-circuiting occurs. In the original post, the evaluation of the left operand terminates the evaluation of the and so the variable's new value isn't available until "after the and is evaluated".

        I guess I could have been more precise. I might have said something like, "the left operand evaluates to undef before being incremented and, since undef is false, the right operand is never evaluated because and is a short-circuit operator." I don't know if that would really have been easier to understand though, even if it is slightly more correct.

        At least, instead of "after the and is evaluated" I probably should have said "after the and is short-circuited" or something.

        Still, the only reason the and was used in the first place was for its short-circuiting properties. Its return value was ignored. In fact, with your example, if I were being pedantic I wouldn't talk about the and at all...

        perl -MO=Deparse -e '$a++ and print $a' print $a if $a++; -e syntax OK
        See? :-)

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: 'and next' question
by Coruscate (Sexton) on Oct 26, 2003 at 18:24 UTC

    Check out this recent node. The examples used contain auto-incrementing a hash :). You should be capable of grasping the concept from the examples in that node. Good luck.


    If the above content is missing any vital points or you feel that any of the information is misleading, incorrect or irrelevant, please feel free to downvote the post. At the same time, please reply to this node or /msg me to inform me as to what is wrong with the post, so that I may update the node to the best of my ability.

Re: 'and next' question
by Anonymous Monk on Oct 26, 2003 at 17:44 UTC
    When do think $hash{$_}++ will return a true value for the logical 'and' to evaluate the other side?

      Here is the explanation:

      Play with the code by changing and to or:

      my %hash; while ( <DATA> ) { chomp; $hash{$_}++ or next if $_ > 5; print; } __DATA__ 3 7 9 1
      The reason is that $hash{$_} ++ resolves to zero (if there is no ++, it resolves to undef), when the first time you encounter that particular key. As he is using the value of $hash{$_} before ++, and $hash{$_} is 0 (not for the second time and afterwards), so the part before and resolves to false. He used and, so the entire condition fails right the way, and there is no point for Perl to evaluate the "next if" part.

      But this particular logic would fail if he has duplicate keys.

      The following would print four 7's for him (not five 7's):

      my %hash; while ( <DATA> ) { chomp; $hash{$_}++ or next if $_ > 5; print; } __DATA__ 3 7 9 1 7 7 7 7
Re: 'and next' question
by hanenkamp (Pilgrim) on Oct 26, 2003 at 20:56 UTC

    Try

    ++$hash{$_} and next if $_ > 5;

    Update: Thanks Anonymous Monk. You're right, I was wrong. Please ignore the rest of this...it doesn't hold water.

    Be careful though about using and this way. Since the precedence of and is extremely low, this is the same as saying:

    # Increment $hash{$_} then if $_ > 5 loop next ++$hash{$_} and (next if $_ > 5);

    When you might rather mean:

    # If $_ > 5 then increment $hash{$_} and loop next (++$hash{$_} and next) if $_ > 5;

    It depends on when you want %hash to be changed. Often, I will avoid statements like this at the cost of brevity to avoid misunderstanding between myself and the compiler on this sort of nuance--or at least use paren's to make the situation explicit.

      ++$hash{$_} and next if $_ > 5;
      Be careful though about using and this way. Since the precedence of and is extremely low, this is the same as saying:
      # Increment $hash{$_} then if $_ > 5 loop next ++$hash{$_} and (next if $_ > 5);
      No, it is not the same at all. The if is a statement modifier and will always be tested first.
      ++$hash{$_} and next if $_ > 5;
      is, as one would expect, the same as:
      $_ > 5 and (++$hash{$_} and next);