1

I have a following statement:

my $iii=0;
while (my @row = sub_undef()) { print $iii++."\n"; }

sub sub_undef {

  if ($error) {
    return "something that will end the while loop";
  } else {
    return 1;
  }
}

In sub_undef() I can have a case (error) when I need to return something that will end while loop. Any ideas how to do it?

-- Dmitry

Horoshiy
  • 17
  • 2
  • 8

4 Answers4

5

An empty array is false1 in boolean context:

return () if $error;

Conveniently, that's what return defaults to, if you don't specify an argument, so you can even just write:

return if $error;

1) A subtlety here is that an empty array evaluates to 0 (that being its length) in scalar context, while an empty list evaluates to undef:

@a = (); $b = @a;  # now $b is 0
$b = ();           # now $b is undef

However, since both 0 and undef are false in boolean context, the difference doesn't really matter here. See perldata for more details.


Edit: If you need to distinguish error conditions from merely running out of data, it's probably better to raise an exception using die for errors and return an empty list for end-of-data. Then your loop would look something like this:

eval {
    while (my @row = sub_undef()) { 
        # do something with @row
    }
};
if ($@) {
    # oops, we got an error, handle it
}

or, using Try::Tiny:

try {
    while (my @row = sub_undef()) { 
        # do something with @row
    }
} catch {
    # oops, we got an error, handle it
};
Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • I am puzzled. I would have used empty `return` to signal 'no more stuff to do' whereas if the termination is due to an error, raising an exception seems better to me. After all, is there any way, short of setting a global or something, to figure out what caused the error with the empty `return`? – Sinan Ünür Apr 12 '13 at 19:17
  • 2
    @Sinan: I read the OP's question as just asking for a way to "return something that will end while loop." You're right that, if the OP needs to distinguish between error and end-of-data conditions, it would make more sense to raise an exception for the former. – Ilmari Karonen Apr 12 '13 at 19:52
  • Re "Conveniently, that's what return defaults to", there's no default here. You pass exactly the same thing to `return` in both cases: an empty list. Do you perhaps think that `()` creates some kind of variable? That would be incorrect. – ikegami Apr 12 '13 at 20:09
  • 1
    Even though `return;` and `return ();` are practically equivalent, I like to be explicit to show that the return value has a purpose. I would only use `return;` when the return value of a subroutine is not used at all. – Dondi Michael Stroma Apr 12 '13 at 20:43
  • @ikegami: The documentation for `return` actually _does_ make a difference between no arguments and an empty list: "_If no `EXPR` is given, returns an empty list in list context, the undefined value in scalar context, and (of course) nothing at all in void context._" I assume this reflects an internal parsing detail; of course, the outcome is the same either way. (In fact, I'm pretty sure that `return ()` is also parsed as "no arguments" rather than as "an expression evaluating to an empty list" anyway, but I felt that making it `return +()` or something would just be pointlessly confusing.) – Ilmari Karonen Apr 13 '13 at 10:34
  • 1
    @Ilmari Karonen, Then it needs fixing because "no arguments" and "empty list" mean the same thing. – ikegami Apr 13 '13 at 14:53
  • @ikegami: I checked, and there indeed _is_ an internal parsing difference between `return ()` and just `return`: the former adds an extra `OP_STUB` to the parse tree. Of course, there's no user-visible difference, because `pp_return`, just as documented, explicitly checks for the case of "scalar context, no arguments" and does exactly what `pp_stub` would've done: pushes an `undef` onto the stack. Which is what I was more or less _trying_ to say above. – Ilmari Karonen Apr 13 '13 at 20:21
  • ... Of course, in list context, `pp_stub` is a no-op, and so your claim that "'no arguments' and 'empty list' mean the same thing" is literally true. What's special about `return`, however, is that unlike ordinary list operators, it does not always evaluate its arguments in list context. In scalar context, `return` and `return ()` _could_ behave differently; it's just a (very good) language design choice that they don't. – Ilmari Karonen Apr 13 '13 at 20:25
  • @Ilmari Karonen, I was aware of the difference in the compiled code. – ikegami Apr 13 '13 at 20:28
  • @Ilmari Karonen, And `return ((((()))))` could also behave differently. But then it wouldn't be Perl. In Perl, `return;`, `return ();` and `return ((((()))));` are all equivalent means of specifying an empty list. – ikegami Apr 13 '13 at 20:30
  • @Ilmari Karonen, Parens never create a list in Perl. Parens are used for precedence, used to force the selection of a list assignment operator (`(...)=...`), and used as a no-op (`@a=;` must be written `@a=();`). – ikegami Apr 13 '13 at 20:34
4

There are a couple of approaches:

Solution 1: If you don't need tor return a row, simply return a false scalar:

sub sub_undef {
    # xxx
    return $error; # Already a true/false scalar
}
while (sub_undef()) { do_stuff(); }

Solution 2: Return an arrayref instead of an array, and false (undef) in case of an error

sub sub_undef {
    # xxx
    return $error ? undef : $rowArrayRef;
}
while (my $row = sub_undef()) { do_stuff(@$row); }

Solution 3: Return a tuple ($status, $rowArrayRef)

sub sub_undef {
    # xxx
    return ($error, $rowArrayRef);
}
while (my ($error, $row) = sub_undef()) { last if $error; do_stuff(@$row); }

Solution 4: Only works if row can NOT be empty unless an error happens based on your business case!

sub sub_undef {
    # xxx
    return $error ? () : @row;
}
while (my @row = sub_undef()) { do_stuff(@row); }

Solution 5: Use exceptions for error handling (Try::Tiny)

# No example worth bothering that won't be covered by Synopsis of the module
DVK
  • 126,886
  • 32
  • 213
  • 327
  • I can't get solution 3 to compile; it doesn't seem that a `while`-condition can contain more than one statement ;-) A for-loop might work better: `for(my($error, $row)=sub_undef(); !$error; ($error, $row)=sub_undef()){...}` – amon Apr 12 '13 at 19:00
  • @amon - oups, you're right. serves me right for trying to needlessly save lines of code. – DVK Apr 12 '13 at 19:18
  • 1
    @ysth That was my first reflex too, but the scope of a lexical variables starts with the next statement. Your `!$error` refers to a global. (The “comma, ok” idiom from Golang is a nice take on error management, but it doesn't map well to Perl.) – amon Apr 12 '13 at 22:04
2

return 1 seems to indicate you really mean to return a scalar. If you wrote the loop as follows

while (my ($value) = iter()) {
   ...
}

then you could signal three states:

  • return $value; for a successful return. Because of the parens in my ($value), you could even safely return something false or undefined without causing the loop to exit.

  • return; for when you iterator has been exhausted. This will cause execution to continue after the loop.

  • die "message" to signal an error. This will cause the program to end unless eval BLOCK is used to catch the exception.

ikegami
  • 367,544
  • 15
  • 269
  • 518
1

Assignment of an empty list will evaluate to false.

  if ($error) {
    return ();
  } else {
    return 1;
  }
Dondi Michael Stroma
  • 4,668
  • 18
  • 21