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

I'm having some trouble with for loops and 'or'. My assumption was that or would evaluate what's on the left, and if it wasn't true what's on the right... I guess this isn't true. So I'm asking what's going on? In real code, 1..11 is the string, but 1..10 could be an array or array slice...
print for (1..10 or 1..11);
why does that print 1..11? If I want I want to print what's on the right, if what's on the right doesn't evaluate as true, is the other option ternary operators? {print for (1..10 ? 1..10 : 1..11);) If you can use an 'or' instead of ternary operators, is it faster?

Replies are listed 'Best First'.
Re: for loops and 'and'
by Zaxo (Archbishop) on Nov 30, 2003 at 06:10 UTC

    Ooh, that's weird. The problem is clearly with precedence and context.

    $ perl -e'print for (1..10 or a..z);' abcdefghijklmnopqrstuvwxyz$
    I have no idea why that doesn't mean print for 1..10;. I can make a case for that meaning print for 10;, but it is crazy that 1..10 evaluates false on the left of low precedence or.

    I'm eagerly anticipating an explanation.

    Update: This prints nothing,

    $ perl -e'print scalar(1..10)' $
    This must be hooked up with the odd special case mentioned by davido that $. is hooked up with flipflops when thay have constant arguments. Dwimmerie gone berserk.

    After Compline,
    Zaxo

      This must be hooked up with the odd special case mentioned by davido that $. is hooked up with flipflops when thay have constant arguments. Dwimmerie gone berserk.
      Not gone berserk, just an awkism. Convenient for processing on ranges of lines, e.g. (1..2)&&/foo/||(3..eof)&&/bar/. What sometimes surprises is that it applies to any constant, even nonnumeric. For instance:
      perl -we'$. = "a"; "b"..die Useless use of range (or flop) in void context at -e line 1. Argument "b" isn't numeric in range (or flip) at -e line 1. Argument "a" isn't numeric in range (or flip) at -e line 1. Died at -e line 1.
      Here it happily compares int("a") to int("b"), giving warnings on both, and then (since both are 0, hence equal) dies. (Though older perls had a bug where it assumed not-equal if no reading of input had ever been done and $. was manually set.)
      I too noticed that, before I posted the question. What's even wierder:
      print "With Parens[", scalar 1..10, "]"; print "\n"; print "Without Parens[",scalar(1..10), "]";
      Perl's range operator has wierd behavior, methinks... but why? Is there anything to fix that in my first case? I'm assuming it'd be a pretty large optimization over ternary operators...

      The problem also existed with arrays that represented ranges, which was even wierder...  do_something $_ for ( @_[1..$#_] || 0..$#{$_[0]}) is the code that I was initially working with to no avail... I guess one could say the problem also is that in the case of for loops, @_[1..$#_] is being used in two contexts at the same time, and perl can't deal with that... The code was basically saying- if it it's there (in scalar context), use it in list context, and if it isn't there, use a diff. list.

      Edit: BazB, added extra code tag.

      I have constructed a more elaborate test -
      use strict; use warnings; use Data::Dumper; # test 1 print for (1..10 or 'a'..'k'); # line 6 print "\n"; # test 2 my @range = 1..10 or 'a'..'k'; # line 10 print for (@range); print "\n"; # test 3 my @r1 = 1..10; my @r2 = 'a'..'k'; @range = @r1 or @r2; # line 17 print for (@range); print "\n";
      And when I ran it, I got the following warnings and results -
      Useless use of range (or flop) in void context at p92.pl line 10. Useless use of private array in void context at p92.pl line 17. Use of uninitialized value in range (or flip) at p92.pl line 6. abcdefghijk 12345678910 12345678910
      The warnings and results might give a clue to what is happenning with list/range and the for and or operators?

      I am wrong. See below. A key question for me is what does or give scalar context to when it is evaluated in list context. I reproduce the results of a test I ran. The results? Mindboggling.


      Who is Kayser Söze?
Re: for loops and 'and'
by ysth (Canon) on Nov 30, 2003 at 08:36 UTC
    Just reread your comment about "In real code". If you have an array instead of the 1..10, the ternary operator is the way to go. print for @a ? @a : 1..11 will have the first @a in scalar context (hence checking if there are any elements in the array) and the second @a in list context, returning the elements of the array. The 1..11 will properly have list context and generate the list (1,2,...,11).

    Array slices are different. They don't have a scalar context per se; if scalar context is imposed (i.e. by being the first operand to ?:) the last element of the slice will be returned (and checked for truth by ?:). So assuming your desired-but-not-working-as-is-code was print for @a[@indices] or 1..11 you need to decide what you mean to do by checking @a[@indices] for truth. That could be any of a number of things. Perhaps one of these would be what you meant:

    print for grep($a[$_], @indices) ? @a[@indices] : 1..11; print for grep(defined $a[$_], @indices) ? @a[@indices] : 1..11; print for grep(exists $a[$_], @indices) ? @a[@indices] : 1..11; print for !grep(!$a[$_], @indices) ? @a[@indices] : 1..11; print for !grep(!defined $a[$_], @indices) ? @a[@indices] : 1..11; print for !grep(!exists $a[$_], @indices) ? @a[@indices] : 1..11; # update: actually use slices after ?
    I.e. use the array if (any/all) of the elements are (true/defined/existing), otherwise fall back on 1..11.
Re: for loops and 'and'
by jweed (Chaplain) on Nov 30, 2003 at 05:50 UTC

    Here's what's happening (I think). The or operator provides scalar context to the first thing it tries (1..10). As the perlop manpage points out, this is false the first time it is tried (I don't really get this...). So for takes 1..11 as its range instead of 1..10.

    As for your "real life" issue with the array slice and the string, my tests show that or never gives scalar context to the second operator, and that it always does to the first. Why? I don't know.

    
    UPDATE
    
    Okay, it seems I didn't understand .. before. From perlop:
    If either operand of scalar ".." is a constant expression, that operand is implicitly compared to the $. variable, the current line number.
    So what happens depends on where it happens. Screwey, eh?

    But don't quote me on that



    Who is Kayser Söze?

      No, this demo defeats what you said: (BTW, just for people who does not know, $. is not the line number of your script, but the line input number of last accessed handler. In the demo, the running line number of __DATA__)

      use strict; use warnings; my $s = "1..11"; <DATA> for (1..5); print for (1..10 or $s); <DATA> for (1..7); print for (1..10 or $s); __DATA__ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

      This code prints 1..11 twice.

        No, this demo defeats what you said
        I'm not sure which part you are responding to. I assume you understand this code, but for others I will point out that the <DATA> for (1..5) leaves $. set to 5 and then <DATA> for (1..7) makes it 12. For neither 1..10 is it equal to 1, so the flipflop returns false.
Re: for loops and 'and'
by ysth (Canon) on Nov 30, 2003 at 08:04 UTC
    As pointed out, on the left operand of the or you have scalar context. List context propagates from the "for" onto the right operand of or. Now the catch: .. is two completely different operators in list vs. scalar context. In scalar context, it is the flipflop operator, and the constants 1 and 10 are implicitly compared to $. (the current readline input number.) In list context, it generates a list from the specified range. So what your code produces is something like:
    my $flipflopcount; print for do { my $flipflop; if (!$flipflopcount && $. == 1) { $flipflop = ++$flipflopcount; } if ($flipflopcount && $. == 10) { $flipflop = ++$flipflopcount . "E0"; undef $flipflopcount; } if ($flipflop) { $flipflop; } else { (1.11); } }
    Try this: perl -wne'print for (1..10 or 1..11)' foo where foo is a file with 12 or so lines. You will get "12345678910E0" as the output for the first 10 lines and "1234567891011" as the output for each line thereafter.

    To the monk confused by the difference between scalar(1..10) and scalar 1..10, that's a precedence thing. The latter is equivalent to scalar(1)..10 so the scalar() is not forcing .. to have scalar context.

Re: for loops and 'and'
by Anonymous Monk on Nov 30, 2003 at 06:28 UTC
    E:\>perl -MO=Deparse,-p -le"print for (1..10 or 1..11);" foreach $_ (((1 .. 10) or (1 .. 11))) { print($_); } -e syntax OK
    That's what's happening.
Re: for loops and 'and'
by dpmott (Scribe) on Nov 30, 2003 at 23:49 UTC
    Wow. I learn something new every day.

    Just wanted to add my $0.02 on this:

    1. perldoc perlop, search for and read the following sections: "C-Style Logical Or" and "Range Operators" (which are conveniently co-located...)

    2. Quote from "C-Style Logical Or":
    @a = @b || @c; # this is wrong @a = scalar(@b) || @c; # really meant this @a = @b ? @b : @c; # this works fine, though
    So, the suggestion of using the ternary operator is a good one. For large arrays, I usually use references, though, if I know that I won't be modifying the array:
    $a = int @b ? \@b : \@c;
    (I use the 'int' operator there for readability. It's superfluous, and does the same thing as the 'scalar' operator for that particular example.

    Also note that the only difference between the '||' operator and the 'or' operator is precedence.

    3. I don't think anyone specifically called it out, but the '..' operator returns a boolean *whenever* it is evaluated in scalar context, not just whenever there is a constant in there. Having the constant in there *also* compares that constant to the '$.' variable. I just wanted to chip in that clarification. Sorry if someone did point that out and I just missed it.

Re: for loops and 'and'
by jonadab (Parson) on Dec 01, 2003 at 15:36 UTC

    for loops have absolutely nothing to do with your problem. Your problem is context. *Lots* of things in Perl behave differently in scalar versus list context. Arrays and array slices report their cardinality in scalar context (except in interpolative context, which is special); in list context they dump their whole contents. List construction operators (the comma list operator and the .. range operator) only *exist* in list context, because Perl doesn't see any point in constructing a list in scalar context. "Why should I construct a list here?", it says, "The very next thing I'd have to do is change it to a scalar somehow. It's utterly pointless to construct a list." So in scalar context these operators do not construct a list; they do Something Else Different.

    as far as for (1..10 or 1..11) versus for (1..10 ? 1..10 : 1..11), the leftmost 1..10 evaluates the same way in both cases; the difference is that if it's true, the latter then evaluates 1..10 in list context; whereas, the former takes the true value and just uses it, which is probably not what you want. Actually, the whole thing is probably not what you want if you didn't specifically intend to use the .. operator in scalar context. Frankly, I'm not sure why you would want to do that; the scalar .. operator is sufficiently obscure that the Camel Book (as of the 2nd edition; I haven't read the 3rd yet) doesn't even *mention* it. It's not a terribly common operator.

    Now, I'm guessing that what you're really attempting in your code is along the lines of for (@a or @b) or maybe for (@a ? @a : @b). The former doesn't do what you want, but you *think* the latter does. But that's an accident; as it happens, the latter isn't working the way you think it is either. The first @a is getting evaluated in boolean context (which is scalar), and so it is returning its *cardinality*. It is not dumping the whole list of its contents like you think. It just happens that a non-empty array or slice will always have a true cardinality (because of the nature of the way cardinality works) and so the second @a will get used when you think it will -- but not for the reason you were thinking, and that's why the simpler or construction doesn't do what you want.

    If you want to be really efficient programming in Perl, if you want to *think* in Perl, you have got to get comfortable thinking in terms of context. Lists and list constructors are not by any means the only things that behave according to context. Many functions (including builtins, object methods in modules from CPAN, and your own functions if you write them that way), certain special variables, and various operators all do different things in different contexts. It's the richness of the context that makes Perl such a flexible and useful language, but if you don't think in terms of context it will confuse you sometimes.

    When you think in terms of context, you won't make these sorts of mistakes. When I first saw your print for (1..10 or 1..11) I wasn't sure exactly what it would do (since I'm not very familiar with the scalar .. operator), but I for darn sure didn't think it would print "12345678910"; it would have surprised me a *great* deal if it had done that. My immediate thought was, "What on earth is he trying to do?" When I read the text of your post and determined that you thought it would print "12345678910", I knew you didn't understand context, because there's no such thing as a list in scalar context; it doesn't even make sense.


    $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/
Re: for loops and 'and'
by dga (Hermit) on Dec 01, 2003 at 18:29 UTC

    One thing I didn't see noted but which may aid understanding. Frequently, in normal use of the boolean operators, they are used for something which has a desired side effect. Then the booleans do something really handy. Here is an example of error notification.

    open my $fh, '<', $file or die "could not open file";

    Here the open is evaluated in scalar context for a true/false value but in order to know if its true or false, perl has to actually try to open the file to see, so the file opening is a side effect of the boolean test. From the programmer point of view the file test is what's going on and the 'or' is for error checking, but from the program flow it is trying to find out if the left side of the or is true or false.

    @array or print "array empty\n";

    This would put the array in scalar context and test for true/false, so if the array has elements then this is true even in the following case.

    @array=undef; @array or print "array empty\n";

    The 'or' never does the print because the assignment made $array[0]=undef;. Thus the array returns true in scalar context and the print isn't done. If you capture the value of this or then you get back the number of elements in the array. Likewise if you capture the value of the open you get back a true value or undef depending on whether the file was opened or not.

    Using 'or' in these places, leads one to feel that actual work can be performed by the boolean operators, but in reality, if work is done, as in the open case, it is the desired side effect of the call and not the boolean comparison that the programmer is interested in occurring. In fact, as a programmer, one might be happier that the right side of the 'or' with the open was never evaluated at all since that means that the program is not encountering error conditions.