$var = do { ... }?

Discussion in 'Perl Misc' started by Tim McDaniel, Mar 2, 2012.

  1. Tim McDaniel

    Tim McDaniel Guest

    I just had a case where there's a block of code, I split up the work
    into assignments to intermediate variables, but I only wanted the
    final value. I very much like to restrict the scope of variables,
    because it's then obvious what's a temporary and what has more
    significance -- it's like an intermediate stage between inline code
    and a sub. How I coded it was

    my $permanent_variable;
    {
    my $this = ...;
    my $that = ... $this ...;
    yadda yadda;
    $permanent_variable = ... final computation ...;
    }

    It occurred to me that I could code it as

    my $permanent_variable = do {
    my $this = ...;
    my $that = ... $this ...;
    yadda yadda;
    ... final computation ...;
    };

    It does do the scope encapsulation that I like, and it makes it
    vividly obvious that the block has one purpose, to set
    $permanent_variable.

    I looked thru the codebase at work and found a few instances of it.
    But mostly "do" was used to implement a slurp function, or otherwise
    to "local()" a variable for one statement or call.

    For people who have looked at lots of different codebases: do people
    think that

    $var = do { several statement; }

    is just an odd construct?

    --
    Tim McDaniel,
    Tim McDaniel, Mar 2, 2012
    #1
    1. Advertising

  2. >>>>> "Tim" == Tim McDaniel <> writes:

    Tim> I looked thru the codebase at work and found a few instances of it.
    Tim> But mostly "do" was used to implement a slurp function, or otherwise
    Tim> to "local()" a variable for one statement or call.

    I do that all the time, although it often suggests that what I really
    have is a subroutine, and I should pull it out and make it one.

    Tim> For people who have looked at lots of different codebases: do people
    Tim> think that

    Tim> $var = do { several statement; }

    Bear in mind that do { } is an expression, so you'll typically need a
    semicolon after that before the next statement (common mistake).

    print "Just another Perl hacker,"; # the original

    --
    Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
    <> <URL:http://www.stonehenge.com/merlyn/>
    Smalltalk/Perl/Unix consulting, Technical writing, Comedy, etc. etc.
    See http://methodsandmessages.posterous.com/ for Smalltalk discussion
    Randal L. Schwartz, Mar 2, 2012
    #2
    1. Advertising

  3. Tim McDaniel

    Tim McDaniel Guest

    In article <>,
    Ben Morrow <> wrote:
    >I agree with Randal on both counts: I use do like this all the time,
    >and you should seriously consider making the block a sub instead.


    Yeah, I tend to do too much inline code and end up with (for example)
    300-line blocks of code. You know, though, that it can be annoying to
    subify something when it has lots of external dependencies, whether
    inputs or outputs.

    >> I looked thru the codebase at work and found a few instances of it.
    >> But mostly "do" was used to implement a slurp function,

    >
    >If you mean the
    >
    > my $txt = do {
    > open my $F, "<", ...;
    > local $/;
    > <$F>;
    > };
    >
    >construction then this is exactly the same situation, isn't it?


    If the *only* use of it is as
    ... = do {local $/; <HANDLE>};
    or the vast majority of use, then it can just be viewed as an idiom
    for one special task. E.g., about the only time I use hash slices is
    my %table;
    @table{@array} = (1) x @array;
    (though I'm reconsidering using instead
    my %table = map { $_ => 1 } @array;
    ).

    --
    Tim McDaniel,
    Tim McDaniel, Mar 2, 2012
    #3
  4. (Tim McDaniel) writes:
    > I just had a case where there's a block of code, I split up the work
    > into assignments to intermediate variables, but I only wanted the
    > final value. I very much like to restrict the scope of variables,
    > because it's then obvious what's a temporary and what has more
    > significance


    [...]

    > my $permanent_variable = do {
    > my $this = ...;
    > my $that = ... $this ...;
    > yadda yadda;
    > ... final computation ...;
    > };
    >
    > It does do the scope encapsulation that I like, and it makes it
    > vividly obvious that the block has one purpose, to set
    > $permanent_variable.


    [...]

    > $var = do { several statement; }
    >
    > is just an odd construct?


    I decidedly do consider this an odd construct: If you have a
    self-contained piece of code whose purpose is to perform some
    operation independently of the surrounding code and to return some
    value, and which possible has a small number of well-defined inputs,
    this should be put into a subroutine with a sufficiently descriptive
    name that someone who reads through the outer code knows what the
    purpose of the subroutine happens to be but without having to bother
    with the details of its implementation and that someone who cares
    about the implementation of this particular subroutine doesn't have to
    go hunting for it in a large block of otherwise unrelated code.

    A real-world example of that:

    sub validate_customer_key($)
    {
    my ($bin_ckey, $ckey, $skey, $skey_version);

    eval {
    ($bin_ckey, $skey_version) = decode_bin_ckey($_[0]);

    $skey = get_server_key($skey_version);
    die("no version $skey_version server key") unless $skey;

    dec($bin_ckey, $skey->key());
    $ckey = MECSUPD::CustomerKey->new_from_string($bin_ckey);
    die("not a valid customer key") unless $ckey;

    check_cookie($ckey, $skey);
    check_expiry($ckey);
    check_ckey_cid_version($ckey);
    };

    $@ && do {
    syslog('ERR', "$@");
    return;
    };

    syslog('INFO', 'customer %08x authenticated successfully',
    $ckey->cid());
    return 1;
    }

    ['dec' means 'decrypt' here]

    This is (not counting external libraries) 'a meeting point' of 80
    lines of Perl code and 20 lines of C code and the algorithm this
    subroutine is supposed to perform would be totally obliterated if the
    100 lines of code implementing all the details had been used in place
    of this 22 lines (sloccount) high-level description.
    Rainer Weikusat, Mar 2, 2012
    #4
  5. Tim McDaniel

    Tim McDaniel Guest

    In article <>,
    Rainer Weikusat <> wrote:
    > [an eval here]
    > $@ && do {
    > syslog('ERR', "$@");
    > return;
    > };


    Is there a reason to prefer that over
    if ($@) {
    syslog('ERR', "$@");
    return;
    }
    ? I don't see a reason, so I prefer the "if" version.

    --
    Tim McDaniel,
    Tim McDaniel, Mar 2, 2012
    #5
  6. >>>>> "Tim" == Tim McDaniel <> writes:

    Tim> Yeah, I tend to do too much inline code and end up with (for example)
    Tim> 300-line blocks of code. You know, though, that it can be annoying to
    Tim> subify something when it has lots of external dependencies, whether
    Tim> inputs or outputs.

    Actually, that'll make your code cleaner. If you have a lot of
    undelimited cohesion (I think that's the word), bugs are much harder to
    find, and the code is more fragile.

    --
    Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
    <> <URL:http://www.stonehenge.com/merlyn/>
    Smalltalk/Perl/Unix consulting, Technical writing, Comedy, etc. etc.
    See http://methodsandmessages.posterous.com/ for Smalltalk discussion
    Randal L. Schwartz, Mar 2, 2012
    #6
  7. Ben Morrow <> writes:
    > Quoth :
    >> In article <>,
    >> Rainer Weikusat <> wrote:
    >> > [an eval here]
    >> > $@ && do {
    >> > syslog('ERR', "$@");
    >> > return;
    >> > };

    >>
    >> Is there a reason to prefer that over
    >> if ($@) {
    >> syslog('ERR', "$@");
    >> return;
    >> }
    >> ? I don't see a reason, so I prefer the "if" version.

    >
    > You should always prefer Try::Tiny over an explicit eval: there are,
    > unfortunately, rather a lot of nasty corner cases in perl's handling of
    > $@, and Try::Tiny deals with as many as it can.


    It is a lot better to state what these corner-cases are than to make
    nebulous scare-mongering statements about them. I'm aware of one: Code
    which is executed as part of a destructor and which doesn't localize
    $@ properly may cause it to be cleared or set to a different value. In
    particular, this will happen when the destructor calls syslog (that's
    were I encountered it). This is, however, not applicable here and in
    any case, the solution is to fix the destructors. I'll happily learn
    about other such cases. But according to the Try::Tiny documentation,
    there are none: The only other thing it mentions is that the 'eval'
    might accidentally clear $@ if it is running inside another eval and
    $@ wasn't properly localized ... but didn't we have this already?

    > If you insist on doing the eval yourself, you should test the truth of
    > the eval
    >
    > eval {
    > ...
    > } or do {
    > ...
    > };
    >
    > rather than relying on $@,


    No, I shouldn't "always do this" because I usually know what the code
    will be doing when executed, ie, in this case, that there are neither
    outer nor inner evals. I understand that this is more difficult for
    someone whose preferred solution to any technical problem is "download
    150,000 lines of unknown code from the internet" (in order to save
    writing 5 lines of code). Regarding this, I'm a follower of the theory
    that blind use of complex devices with unknown properties (third-party
    written maxi-mega-modules intended to solve 15,000 different trivial
    problems with a huge amount of "heavily optimized general-purpose
    code") is bound to cause accidental deaths and other nuisances and
    therefore, I avoid such situations (fixing the inevitable bugs in a
    large body of unknown code is going to take more time than writing a
    small amount of new code).
    Rainer Weikusat, Mar 2, 2012
    #7
  8. (Tim McDaniel) writes:
    > In article <>,
    > Rainer Weikusat <> wrote:
    >> [an eval here]
    >> $@ && do {
    >> syslog('ERR', "$@");
    >> return;
    >> };

    >
    > Is there a reason to prefer that over
    > if ($@) {
    > syslog('ERR', "$@");
    > return;
    > }
    > ? I don't see a reason, so I prefer the "if" version.


    I'm not aware of any, that's IMHO just a matter of personal
    preference.
    Rainer Weikusat, Mar 2, 2012
    #8
  9. Rainer Weikusat <> writes:
    > Ben Morrow <> writes:
    >> Quoth :
    >>> In article <>,
    >>> Rainer Weikusat <> wrote:
    >>> > [an eval here]
    >>> > $@ && do {
    >>> > syslog('ERR', "$@");
    >>> > return;
    >>> > };
    >>>
    >>> Is there a reason to prefer that over
    >>> if ($@) {
    >>> syslog('ERR', "$@");
    >>> return;
    >>> }
    >>> ? I don't see a reason, so I prefer the "if" version.

    >>
    >> You should always prefer Try::Tiny over an explicit eval: there are,
    >> unfortunately, rather a lot of nasty corner cases in perl's handling of
    >> $@, and Try::Tiny deals with as many as it can.

    >
    > It is a lot better to state what these corner-cases are than to make
    > nebulous scare-mongering statements about them.


    To state this in a clearer way: The two cases this module is supposed
    to deal with are

    -------------
    package a;

    sub DESTROY
    {
    eval { 3 + 1; };
    }

    package main;

    eval {
    my $a;
    bless(\$a, 'a');

    die("gruesome error!");
    };

    $@ and print("$@\n");
    -------------

    Upon exiting the eval scope, the a::DESTROY routine will be executed
    automatically and the eval in there cause $@ to be cleared. The
    solution to this problem is to add a

    local $@ if $@

    to the beginning of any destructor which does something none-trivial
    which might either invoke die or eval. Since a destructor can be
    executed automatically after some other code died but before the
    unknowing caller had a chance to look at $@, it certainly shouldn't
    change an existing value of $@.

    The other is

    --------------
    sub complex_task
    {
    eval { 3 + 1; };
    }

    eval {
    die("gruesome error!");
    };

    complex_task();

    $@ and print("$@\n");
    ---------------

    This time, the caller is at fault: If some non-trivial action needs to
    be performed before looking at $@, the value of $@ immediately after
    the eval needs to be save in a 'non-global' variable until it is going
    to be used. This is especially true because there is, as the
    'BACKGROUND' text of the Try::Tiny documentation aptly explains, no
    way the called routine can easily do this.

    IMO, both of these 'nasty corner-cases' are actually fairly trivial
    programming errors and the general solution to these is to teach
    people how to avoid them, not to try to write code which enables them
    to remain blissfully unaware of the problem.
    Rainer Weikusat, Mar 2, 2012
    #9
  10. On Fri, 02 Mar 2012 20:43:17 +0000, Rainer Weikusat wrote:

    > Ben Morrow <> writes:


    >> If you insist on doing the eval yourself, you should test the truth of
    >> the eval
    >>
    >> eval {
    >> ...
    >> } or do {
    >> ...
    >> };
    >>
    >> rather than relying on $@,

    >
    > No, I shouldn't "always do this" because I usually know what the code
    > will be doing when executed, ie, in this case, that there are neither


    If there is a clear way that is always correct and another equally clear
    way that may break on refactoring, I always would choose the first. This
    seems like one of those cases (which I didn't know about yet btw).

    M4
    Martijn Lievaart, Mar 3, 2012
    #10
  11. Martijn Lievaart <> writes:
    > On Fri, 02 Mar 2012 20:43:17 +0000, Rainer Weikusat wrote:
    >
    >> Ben Morrow <> writes:

    >
    >>> If you insist on doing the eval yourself, you should test the truth of
    >>> the eval
    >>>
    >>> eval {
    >>> ...
    >>> } or do {
    >>> ...
    >>> };
    >>>
    >>> rather than relying on $@,

    >>
    >> No, I shouldn't "always do this" because I usually know what the code
    >> will be doing when executed, ie, in this case, that there are neither

    >
    > If there is a clear way that is always correct and another equally clear
    > way that may break on refactoring, I always would choose the first. This
    > seems like one of those cases (which I didn't know about yet btw).


    Testing $@ is 'always correct' while the return value of eval might be
    'false' for any number of reasons because it is just the return value
    of the last thing executed in the scope of the eval.

    ----------
    my $bc;

    my $rc = eval {
    $bc = 3;
    die('huh?') if $bc != 3;
    } or do {
    print("Ben Morrow error occurred!\n");
    $@ or print("Phew ... that was close ... \n");
    };
    ----------

    Also, there is no way to write code such that yet unknown future
    changes made to this code are guaranteed to be correct.
    Rainer Weikusat, Mar 3, 2012
    #11
  12. Rainer Weikusat <> writes:
    > Martijn Lievaart <> writes:
    >> On Fri, 02 Mar 2012 20:43:17 +0000, Rainer Weikusat wrote:
    >>
    >>> Ben Morrow <> writes:

    >>
    >>>> If you insist on doing the eval yourself, you should test the truth of
    >>>> the eval
    >>>>
    >>>> eval {
    >>>> ...
    >>>> } or do {
    >>>> ...
    >>>> };
    >>>>
    >>>> rather than relying on $@,
    >>>
    >>> No, I shouldn't "always do this" because I usually know what the code
    >>> will be doing when executed, ie, in this case, that there are neither

    >>
    >> If there is a clear way that is always correct and another equally clear
    >> way that may break on refactoring, I always would choose the first. This
    >> seems like one of those cases (which I didn't know about yet btw).

    >
    > Testing $@ is 'always correct' while the return value of eval might be
    > 'false' for any number of reasons because it is just the return value
    > of the last thing executed in the scope of the eval.


    Additional remark: One of the purposes of using exceptions to signal
    errors is to avoid the so-called semipredicate problem where a
    technically legitimate return value must be used to signal an
    exceptional condition and the caller cannot generally tell the
    difference. Eg, assume the eval returns a file descriptor number. The
    usual convention for signalling errors via return value for
    subroutines doing this would be to use the number -1 which cannot be a
    valid file descriptor. But this won't work with the code above because
    -1 is logically true. OTOH, 0 is a valid file descriptor number
    (usually used for 'the standard input file descriptor') but logically
    false.
    Rainer Weikusat, Mar 3, 2012
    #12
  13. On Sat, 03 Mar 2012 17:11:56 +0000, Rainer Weikusat wrote:

    > Testing $@ is 'always correct' while the return value of eval might be
    > 'false' for any number of reasons because it is just the return value of
    > the last thing executed in the scope of the eval.


    I think I get your point. You *have* to make sure all exception handlers
    are correct (not a biggy) because otherwise you risk screwing up your
    evals. Right?

    M4
    Martijn Lievaart, Mar 3, 2012
    #13
  14. Ben Morrow <> writes:
    > Quoth Martijn Lievaart <>:
    >> On Sat, 03 Mar 2012 17:11:56 +0000, Rainer Weikusat wrote:
    >>
    >> > Testing $@ is 'always correct' while the return value of eval might be
    >> > 'false' for any number of reasons because it is just the return value of
    >> > the last thing executed in the scope of the eval.

    >
    > Which is why you should use Try::Tiny, which fixes this for you.


    You mean it screws up the return value of an eval?

    >> I think I get your point. You *have* to make sure all exception handlers
    >> are correct (not a biggy) because otherwise you risk screwing up your
    >> evals. Right?

    >
    > Localising $@ in DESTROY would not be sufficient, even if you could rely
    > on all destructors in all code you use doing so.


    It is never possible to rely on the fact that some code isn't
    buggy. The solution is to fix the bugs AND NOT to download more
    (presumably buggy) code which tries to work around them.

    > See the BACKGROUND section of Try::Tiny's documentation.


    Hic Rhodos, hic salta: What are you precisely referring to? I read
    this background section and wrote about the two things I found in
    there which were not just handwaiving. So, what did I miss and/or get
    wrong? According to your opinion, not according to a random text some
    random guy uploaded to a random web server in 2009(!), presumably more
    than fifteen years after people started to use exception handling in
    Perl despite you claim that's impossible without the 2009 guy.
    Rainer Weikusat, Mar 4, 2012
    #14
  15. Tim McDaniel

    Dr.Ruud Guest

    On 2012-03-02 21:10, Ben Morrow wrote:

    > If you insist on doing the eval yourself, you should test the truth of
    > the eval
    >
    > eval {
    > ...
    > } or do {
    > ...
    > };
    >
    > rather than relying on $@, since there are cases (destructors, for one,
    > depending on your version of perl) where $@ can be cleared even though
    > the eval failed. You still lose the error, but at least you know there
    > was one.


    Indeed, testing $@ is a bad practice.

    Your code snippet is missing some important features, see:

    my $ok;
    eval {
    $ok = foo();
    1; # success
    }
    or do { # exception
    my $eval_error = $@ || 'zombie error';
    ...;
    };

    --
    Ruud
    Dr.Ruud, Mar 5, 2012
    #15
  16. "Dr.Ruud" <> writes:
    > On 2012-03-02 21:10, Ben Morrow wrote:
    >> If you insist on doing the eval yourself, you should test the truth of
    >> the eval
    >>
    >> eval {
    >> ...
    >> } or do {
    >> ...
    >> };
    >>
    >> rather than relying on $@, since there are cases (destructors, for one,
    >> depending on your version of perl) where $@ can be cleared even though
    >> the eval failed. You still lose the error, but at least you know there
    >> was one.

    >
    > Indeed, testing $@ is a bad practice.
    >
    > Your code snippet is missing some important features, see:
    >
    > my $ok;
    > eval {
    > $ok = foo();
    > 1; # success
    > }
    > or do { # exception
    > my $eval_error = $@ || 'zombie error';
    > ...;
    > };


    But this does nothing but 'test $@' in order to determine if an error
    occurred. There's just an additional bit of useless code wrapped
    around it which is based on the (unjustified) assumption that the
    value returned by the last thing evaluated inside the eval will never
    be 0, '' or undef. Essentially, this amounts abandoning the concept of
    using 'exceptions' for out-of-band error signalling and going back to
    implicit 'special return value' conventions instead while retaining all
    the run-time overhead of the exception mechanism. This can be achieved
    in a much clearer way by simply not using exceptions and maybe arguing
    in public that anything but using special return-value conventions for
    error signalling would be 'bad practice' (because of ...)

    OTOH, "that's your opinion" and some people don't agree with that,
    cf

    http://perl.apache.org/docs/general...eference.html#Exception_Handling_for_mod_perl

    Now, where's the 'foaming mouth guy' crying "This is deprecated !
    Deprected !! Deprecated !!! Die, sinner, die !!!!"?

    Can't you just accept that your vision for Perl is by no means
    universal and that people use Perl in many different ways, no matter
    if J. Random Multicolor Loser who wasn't asked for his insignificant
    opinion to begin with approves of that?

    Go Python, "there's only my way or the highway" guy. That's where you
    belong to.
    Rainer Weikusat, Mar 5, 2012
    #16
  17. Rainer Weikusat <> writes:
    > Ben Morrow <> writes:


    [...]


    >> Localising $@ in DESTROY would not be sufficient, even if you could rely


    [...]

    >> See the BACKGROUND section of Try::Tiny's documentation.

    >
    > Hic Rhodos, hic salta: What are you precisely referring to? I read
    > this background section and wrote about the two things I found in
    > there which were not just handwaiving. So, what did I miss and/or get
    > wrong?


    Since (somewhat expectedly) nothing came of that and I've re-read the
    Try::Tiny BACKGROUND section meanwhile with more attention to detail,
    I now confidently state that the 'Localizing ... would not be
    sufficient' statement is wrong. Adressing each of the 'rationales' in
    turn:

    ,----
    | Clobbering $@
    |
    | When you run an eval block and it succeeds, $@ will be
    | cleared, potentially clobbering an error that is currently
    | being caught.
    |
    | [...]
    |
    | $@ must be properly localized before invoking eval in order to avoid
    | this issue.
    `----

    As I already wrote: This is the documented behaviour of eval and it is
    the responsibility of the code which is interested in the content of
    $@ to store that in some safe place before invoking other code which
    might cause the value of $@ to be changed. That's a side effect of the
    design descision to use a global variable to store the most recently
    thrown exception. Taking this into account, this design descision can
    certainly be called 'somewhat unfortunate', however, this is argueing
    about spilt milk: perl behaves in the way it does and application
    code written in Perl needs to take this into account. It is in no way
    sensible to burden code which is regularly executed with working
    around possible errors in the calling code, as suggested in the '$@
    must be properly localized'.

    ,----
    | Localizing $@ silently masks errors
    |
    | Inside an eval block die behaves sort of like:
    |
    | sub die {
    | $@ = $_[0];
    | return_undef_from_eval();
    | }
    |
    | This means that if you were polite and localized $@ you can't die in
    | that scope, or your error will be discarded (printing "Something's
    | wrong" instead).
    `----

    .... not only is the idea to always localize $@ 'just in case' before
    executing eval not sensible, it additionally breaks exception
    propagation out of the current lexical scope. But since it is only
    necessary to work around this problematic side effect when proactively
    trying to work around the non-problem described in the previous
    paragraph, this is not generally an issue.

    ,----
    | $@ might not be a true value
    |
    | This code is wrong:
    |
    | if ( $@ ) {
    | ...
    | }
    |
    | because due to the previous caveats it may have been unset.
    `----

    But there were no such 'previous caveats', just a remark about the
    documented behaviour of eval and how that may interact badly with some
    calling code written based on the wrong assumption that $@ would not
    be a global variable. Actually, $@ can't be 'unset' except as side
    effect of code which runs between the time of the original die and the
    time the caller looks at $@. Minus the already mentioned 'caller bug'
    of not saving $@, this leaves a single possible problem situation,
    namely,

    ,----
    | The classic failure mode is:
    |
    | sub Object::DESTROY {
    | eval { ... }
    | }
    |
    | eval {
    | my $obj = Object->new;
    |
    | die "foo";
    | };
    |
    | if ( $@ ) {
    |
    | }
    `----

    This is an actual problem because destructors can be executed after a
    die and before the caller of the eval ever gets a chance to look at
    $@. As I already wrote, because of this, a destructor which doesn't
    localize $@ if it already has a value before executing code which
    might either eval or die is broken. It is possible that the value in
    $@ isn't interesting to the (indirect) caller anymore and that the
    destructor just sees it because $@ is a global variable, but there's
    no way to distinguish between these two cases.

    Lastly,

    ,----
    | The workaround for this is even uglier than the previous ones. Even
    | though we can't save the value of $@ from code that doesn't localize,
    | we can at least be sure the eval was aborted due to an error:
    |
    |
    | my $failed = not eval {
    | ...
    |
    | return 1;
    | };
    `----

    there's little point in disabling useful features (out-of-band error
    signalling, eval return values) in order to work around hypothetical
    bugs in destructors. Instead, the buggy destructors need to be fixed.
    Rainer Weikusat, Mar 5, 2012
    #17
  18. Tim McDaniel

    Tim McDaniel Guest

    In article <jiqv7d$6k4$>,
    Tim McDaniel <> wrote:
    >It occurred to me that I could code it as
    >
    > my $permanent_variable = do {
    > my $this = ...;
    > my $that = ... $this ...;
    > yadda yadda;
    > ... final computation ...;
    > };


    So I have to make sure the code evaluates the desired return value as
    the last thing in the block, like

    my $result = do {
    if ($i % 2 == 0) { 'even' }
    elsif ($i % 3 == 0) { 'divisible by 3' }
    elsif ($i % 5 == 0) { 'divisible by 5' }
    else { 'just wrong' }
    };

    Is there a clever way in Perl 5 to metaphorically return early with a
    value?

    "return", in Perl 5.8 and 5.14, returns from a sub, not a block.

    "last" is documented in Perl 5.8.8 as

    "last" cannot be used to exit a block which returns a value such
    as "eval {}", "sub {}" or "do {}", and should not be used to exit
    a grep() or map() operation.

    Note that a block by itself is semantically identical to a loop
    that executes once. Thus "last" can be used to effect an early
    exit out of such a block.

    So I can use "last" to end early, if I wrap it in an extra {...}.
    But "last" doesn't return a value: if I just do "last", the do{...}
    experimentally evaluates to undef.

    So far as I can see, I can exit a do{...} early AND return a value
    only by coding it myself using an extra {...}, like

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    #! /usr/bin/perl
    use strict;
    use warnings;

    for my $i (4, 9, 25, -1) {
    my $result = do {
    my $return;
    {
    if ($i % 2 == 0) { $return = 'even'; last };
    if ($i % 3 == 0) { $return = 'divisible by 3' }
    elsif ($i % 5 == 0) { $return = 'divisible by 5' }
    else { $return = 'just wrong' }
    }
    $return;
    };
    print $i, (defined $result ? " defined $result" : ' undef '), "\n";
    }
    exit 0;

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    (Of course that's contrived; the original if-elsif-else structure is
    a much more concise way of expressing it.)

    Is there a clever way in Perl 5 to metaphorically return early with a
    value?

    --
    Tim McDaniel,
    Tim McDaniel, Mar 15, 2012
    #18
  19. Tim McDaniel

    Dr.Ruud Guest

    On 2012-03-15 18:09, Tim McDaniel wrote:

    > So I have to make sure the code evaluates the desired return value as
    > the last thing in the block, like
    >
    > my $result = do {
    > if ($i % 2 == 0) { 'even' }
    > elsif ($i % 3 == 0) { 'divisible by 3' }
    > elsif ($i % 5 == 0) { 'divisible by 5' }
    > else { 'just wrong' }
    > };
    >
    > Is there a clever way in Perl 5 to metaphorically return early with a
    > value?


    That if/elsif/else of yours already does that. Remember that
    perl-the-binary compiles to opcodes. So the 'elsif' is only done if the
    'if' didn't do.

    perl -Mstrict -wle '
    my $i = $ARGV[0];
    my $result = !$i ? "0"
    : !($i % 2) ? "2-fold"
    : !($i % 3) ? "3-fold"
    : !($i % 5) ? "5-fold"
    : "bah";
    print $result;
    ' -- -21
    3-fold

    --
    Ruud
    Dr.Ruud, Mar 16, 2012
    #19
  20. Ben Morrow <> writes:
    > Quoth :


    [...]

    >> my $result = do {
    >> if ($i % 2 == 0) { 'even' }
    >> elsif ($i % 3 == 0) { 'divisible by 3' }
    >> elsif ($i % 5 == 0) { 'divisible by 5' }
    >> else { 'just wrong' }
    >> };
    >>
    >> Is there a clever way in Perl 5 to metaphorically return early with a
    >> value?


    [...]

    > The other thing that works, and it is in fact documented though I had no
    > idea until I just looked, is to return from an eval {}:
    >
    > my $result = eval {
    > $_ % 2 == 0 and return "even";
    > $_ % 3 == 0 and return "divisible by three";
    > return "just wrong";
    > };
    >
    > I'm not sure it's got much to recommend it over
    >
    > my $result = sub {
    > $_ % 2 == 0 and return "even";
    > return "odd";
    > }->();
    >
    > though,


    The first is using a language construct according to its intended
    purpose. The second is abusing a language construct in order to
    emulate the first 'somehow'. That alone should be sufficient to avoid
    it. In addition to that, it needs more test because the mock
    subroutine created for this purpose also needs to be invoked and -
    depending on whether the compiler special-cases this so that people
    can indulge their passion for the bizarre[*] - it is probably also
    less efficient.

    [*] 'Xah Lee' should like it, however, since it is quite
    'mathy' ...
    Rainer Weikusat, Mar 16, 2012
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Alvin Bruney

    Threads.. Session var lost, App var ok

    Alvin Bruney, Dec 2, 2003, in forum: ASP .Net
    Replies:
    1
    Views:
    359
    rooster575
    Dec 2, 2003
  2. thomson
    Replies:
    10
    Views:
    2,493
    Eliyahu Goldin
    Jun 20, 2005
  3. thomson
    Replies:
    0
    Views:
    379
    thomson
    Jun 20, 2005
  4. Fred
    Replies:
    3
    Views:
    325
    Alf P. Steinbach
    Aug 10, 2003
  5. Alex Vinokur
    Replies:
    4
    Views:
    473
    Ron Natalie
    Sep 24, 2003
Loading...

Share This Page