3

While reading a book on advanced Perl programming(1), I came across this code:

while (defined($s = <>)) {
    ...

Is there any special reason for using defined here? The documentation for perlop says:

In these loop constructs, the assigned value (whether assignment is automatic or explicit) is then tested to see whether it is defined. The defined test avoids problems where line has a string value that would be treated as false by Perl, for example a "" or a "0" with no trailing newline. If you really mean for such values to terminate the loop, they should be tested for explicitly: [...]

So, would there be a corner case or that's simply because the book is too old and the automatic defined test was added in a recent Perl version?


(1) Advanced Perl Programming, First Edition, Sriram Srinivasan. O'Reilly (1997)

cxw
  • 16,685
  • 2
  • 45
  • 81
sidyll
  • 57,726
  • 14
  • 108
  • 151

3 Answers3

8

Perl has a lot of implicit behaviors, many more than most other languages. Perl's motto is There's More Than One To Do It, and because there is so much implicit behavior, there is often More Than One Way To express the exact same thing.

/foo/ instead of $_ =~ m/foo/

$x = shift instead of $x = shift @_

while (defined($_=<ARGV>)) instead of while(<>)

etc.

Which expressions to use are largely a matter of your local coding standards and personal preference. The more explicit expressions remind the reader what is really going on under the hood. This may or may not improve the readability of the code -- that depends on how knowledgeable the audience is and whether you are using well-known idioms.

In this case, the implicit behavior is a little more complicated than it seems. Sometimes perl will implicitly perform a defined(...) test on the result of the readline operator:

$ perl -MO=Deparse -e 'while($s=<>) { print $s }'
while (defined($s = <ARGV>)) {
    print $s;
}
-e syntax OK

but sometimes it won't:

$ perl -MO=Deparse -e 'if($s=<>) { print $s }'
if ($s = <ARGV>) {
    print $s;
}
-e syntax OK

$ perl -MO=Deparse -e 'while(some_condition() && ($s=<>)) { print $s }'
while (some_condition() and $s = <ARGV>) {
    print $s;
}
-e syntax OK

Suppose that you are concerned about the corner cases that this implicit behavior is supposed to handle. Have you committed perlop to memory so that you understand when Perl uses this implicit behavior and when it doesn't? Do you understand the differences in this behavior between Perl v5.14 and Perl v5.6? Will the people reading your code understand?

Again, there's no right or wrong answer about when to use the more explicit expressions, but the case for using an explicit expression is stronger when the implicit behavior is more esoteric.

Community
  • 1
  • 1
mob
  • 117,087
  • 18
  • 149
  • 283
  • 2
    I also noticed that the answer to the actual question was buried in the last paragraph. – darch Mar 08 '12 at 18:07
  • 1
    It's very clear when `/foo/` means `$_ =~ /foo/`, but it's not nearly as clear when `<>` means `defined($_ = <>)`. I don't consider them the same. – ikegami Mar 08 '12 at 21:05
4

Say you have the following file

4<LF>
3<LF>
2<LF>
1<LF>
0

(<LF> represents a line feed. Note the lack of newline on the last line.)

Say you use the code

while ($s = <>) {
   chomp;
   say $s;
}

If Perl didn't do anything magical, the output would be

4
3
2
1

Note the lack of 0, since the string 0 is false. defined is needed in the unlikely case that

  • You have a non-standard text file (missing trailing newline).
  • The last line of the file consists of a single ASCII zero (0x30).

BUT WAIT A MINUTE! If you actually ran the above code with the above data, you would see 0 printed! What many don't know is that Perl automagically translates

while ($s = <>) {

to

while (defined($s = <>)) {

as seen here:

$ perl -MO=Deparse -e'while($s=<DATA>) {}'
while (defined($s = <DATA>)) {
    ();
}
__DATA__
-e syntax OK

So you technically don't even need to specify defined in this very specific circumstance.

That said, I can't blame someone for being explicit instead of relying on Perl automagically modifying their code. After all, Perl is (necessarily) quite specific as to which code sequences it will change. Note the lack of defined in the following even though it's supposedly equivalent code:

$ perl -MO=Deparse -e'while((), $s=<DATA>) {}'
while ((), $s = <DATA>) {
    ();
}
__DATA__
-e syntax OK
cxw
  • 16,685
  • 2
  • 45
  • 81
ikegami
  • 367,544
  • 15
  • 269
  • 518
-2
while($line=<DATA>){
    chomp($line);
if(***defined*** $line){
    print "SEE:$line\n";
}
}
__DATA__
1
0
3

Try the code with defined removed and you will see the different result.

aartist
  • 3,145
  • 3
  • 33
  • 31
  • I know it will be different, but that's not the case. The question is about using `defined` inside the loop conditional. – sidyll Mar 08 '12 at 16:58
  • 2
    -1 for each of not answering the question and providing non-compiling code. – darch Mar 08 '12 at 18:06
  • Perl automagically replaces `while($line=)` with `while(defined($line=))`, so you're obviously talking about something different than the OP if you see something different. (See the output of `perl -MO=Deparse -e'while($line=) {}'`) – ikegami Mar 08 '12 at 20:49