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

I understand that doing
while (<FH>) ...
is the same as doing
while ($_=<FH>) ...
which, in turn, is the same as doing
while (defined($_=<FH>)) ...
so far, so good. But then why the camel book says that this is suboptimal:
if ($_=<FH>) ...
and this is optimal:
if (defined($_=<FH>)) ...
As far as I understand, $_=<FH> will assign to $_ the line read from FH, or an undefined value if we happen to be at the end of file FH. undefined is evaluated as false in boolean context. So why is the first idiom "suboptimal"? Thanks for any help.

Update: Ok, many thanks to everybody for your replies.

Replies are listed 'Best First'.
Re: Doubt on defined-ness
by almut (Canon) on Jun 18, 2008 at 19:06 UTC
    So why is the first idiom "suboptimal"?

    I haven't looked up that passage in the Camel Book, so I'm not sure what it's referring to... but maybe it's trying to point out that 'if ($_=<FH>) ...' could evaluate to false in circumstances where you don't want it to be false (the line being empty or zero). Just a guess though.

    (Update: crossed out "empty", because - upon rethinking - I can't really come up with any case where $_ would be the emtpy string — not even when fiddling with the input record separator...)

    Update 2: ikegami actually did find a variant where <> can return an empty string...  So, I might in theory un-strike that part again. But I won't — I guess it's unlikely anyone will run into this case by accident :)

      This isn't quite you had in mind, but not only did I find a case where <> returns an empty string, it does so before the last "line".

      >perl -MData::Dumper -e"while (<{,0,a}>) { print Dumper $_ }" $VAR1 = ''; $VAR1 = '0'; $VAR1 = 'a';

      A tied handle could do it too, say one that autochomps.

      Maybe on a non-blocking handle?

        Interesting idea... but I can't even get that to return an empty string:

        #!/usr/bin/perl use Fcntl; fcntl(STDIN, F_GETFL, my $flags) or die "Couldn't get flags: $!\n"; fcntl(STDIN, F_SETFL, $flags|O_NONBLOCK) or die "Couldn't set flags: $ +!\n"; $_=<STDIN>; use Data::Dumper; print Dumper $_; # --> prints $VAR1 = undef;

        Maybe you can construct a better case (?)

      I remember I brought up that bug, back in the days that defined was not yet implied...

      If your text file ended with a "0" on a line of its own, without appended newline, that last line was ignored.

      Worse: if that happened in a file in the middle of a list of files in @ARGV, then while(<>) was interrupted at the end of that file, and the rest of the files that came after it were simply ignored.

      As for the Camel Book: historically, originally defined was not implied. The text in the book probably just still reflects that.

Re: Doubt on defined-ness
by pc88mxer (Vicar) on Jun 18, 2008 at 19:07 UTC
    The condition will be false if $_ is undef or 0 or the empty string (or whatever else perl deems to evaluate to false.)

    So, if your last line is not terminated by a newline and is the string "0", you'll get tripped on this if don't for defined-ness.

    Update: Considering the discussion below, I should have made it clear that this is a potential problem only for if statements (which is what the OP asked about), not while statements.

    What is interesting is that even though the condition expression for while and if statements is executed in "boolean" context, the magic of the diamond operator only happens in while statements.

    use strict; use warnings; use Contextual::Return; use Carp; while (foo()) { last; } while (my $x = foo()) { last; } if (foo()) { } if (my $z = foo()) { } sub foo { return BOOL { carp "BOOL"; 1; } SCALAR { carp "SCALAR"; 1; } ; } __END__ BOOL at ./cr line 6 BOOL at ./cr line 7 BOOL at ./cr line 8 BOOL at ./cr line 9
      Actually I was about to answer just that, but then I tested it - and it turned out I was wrong.

      Consider this:

      $ hexdump -C foo 00000000 61 0a 30 |a.0| 00000003 $ perl -wle 'print "read line" while (my $x = <>)' foo read line read line $

      I have a file foo here with an 'a', a newline, and a '0' - no trailing newline.

      I'd expect just one line of output, because 0 is false. But why am I getting two lines? That doesn't seem to be logical to me.

      So I modified my test scrpt a bit:

      $ perl -MDevel::Peek -wle 'while (my $x = <>){ print "read line"; pri +nt "and its true" if $x }' foo read line and its true read line

      So, the second line evaluates to true in the while-condition, but not in the following if-condition. Is there some kind of special magic? If yes, where is it documented?

      Update ok, got it. Thank you all. I didn't realize it works for arbitrary variable names, and thought that $_ was special cased.

        It's because perl applies its magic to your while. Deparse shows it as
        while (defined(my $x = <ARGV>))


        Unless I state otherwise, all my code runs with strict and warnings
        It's just syntactic sugar. Perlop says:
               The following lines are equivalent:
        
                   while (defined($_ = <STDIN>)) { print; }
                   while ($_ = <STDIN>) { print; }
                   while (<STDIN>) { print; }
        
        
        So, the second line evaluates to true in the while-condition, but not in the following if-condition. Is there some kind of special magic? If yes, where is it documented?
        the only location where i found it mentioned after a quick search is in perlvar $_:
        $ARG $_ The default input and pattern-searching space. The following pairs are equivalent: while (<>) {...} # equivalent only in while! while (defined($_ = <>)) {...}
        edit: and in perlsyn
        edit: ah, it's in perlop, like betterworld mentioned. overlooked it.
      Well, ok for the non-newline terminated lines (I hadn't thought about that case), but can really $_ be undefined after $_=<FH> if we are not at eof?
Re: Doubt on defined-ness
by FunkyMonk (Bishop) on Jun 18, 2008 at 19:18 UTC
    while (<FH>) ...

    is the same as doing

    while ($_=<FH>) ...
    Not quite. while (<FH>) is shorthand for
    while (defined($_ = <FH>))

    Which should clear up the other half of your confusion.


    Unless I state otherwise, all my code runs with strict and warnings
      The camel book lists the following (among others) as being equivalent:
      while (defined($_=<STDIN>)) ... while ($_=<STDIN>) ... while (<STDIN>) ...
      so I guess that only in the case of a while both the second and third form are equivalent to the first. This is not true for other boolean context like eg if.
        It seems perl applies its magic test for definedness to any while that involves a filehandle (at least, I couldn't find a case where it didn't apply).
        $ perl -MO=Deparse -e 'while (<>) {} while ( <ARGV> ) {} while ( $_ = +<> ) {} while ( $x = <> ) {}' while (defined($_ = <ARGV>)) { (); } while (defined($_ = <ARGV>)) { (); } while (defined($_ = <ARGV>)) { (); } while (defined($x = <ARGV>)) { (); } -e syntax OK


        Unless I state otherwise, all my code runs with strict and warnings