$var = do { ... }?

T

Tim McDaniel

That if/elsif/else of yours already does that.

Yes, I know. I was giving an example where the desired final value is
the last thing evaluated, and then asking what I could do if that
happens not to be the case. The next (badly contrived) example I gave
was such a case, with the 'even' case returning early and the first
"elsif" changed to an "if".
my $result = !$i ? "0"
: !($i % 2) ? "2-fold"
: !($i % 3) ? "3-fold"
: !($i % 5) ? "5-fold"
: "bah";

My question was prompted bya ?: chain like that, except with much
larger and more complicated expressions, and embedded in a much larger
and much more complicated expression. I began idly musing on
different ways it could be avoided, other than the obvious ones of
factoring it into a sub, moving it to an if-elsif-else above the much
more complicated block, and so forth.
 
R

Rainer Weikusat

Dr.Ruud said:
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.

"When the program compiles, the machine is happy" (R. Pike). But since
code is not write-only, there should be other considerations beyond
'making the machine happy'. The way and if - else construct works is
that it provides, based on some set of conditions, a set of
alternative codepaths to follow at some point of an otherwise linear
movement, ie, after one of the alternate code paths was chosen,
execution is supposed to continue with the next statement after the
if. People tend to use it even if no such next statement exists, at
least partially cause by a misunderstanding of some statement about
'structure programming', namely, that 'a module' (subroutine) should
have a single entry and exit point. This essentially doesn't make any
sense[*] in language designed with this dictum in mind because
subroutines always have a single entry point and they always have a
single exit point (that's the location where execution of the calling
code continues after the subroutine finished(!)). Later, this
statement has been re-interpreted to mean that the control-flow inside
a subroutine should be contorted to give the illusion as if all
possible codepaths through the subroutine would 'naturally' end after
the last statement in the corresponding block. And that's IMO not a
good idea.
 
T

Tim McDaniel

Ben Morrow said:
Quoth (e-mail address removed):
[...]
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.

I infer that "first" means the eval example and "second" means the sub
example.

The notion of "abusing a language construct" in Perl is already a
problematic notion. Perl is the polyperverted abuse bottom of
languages.

The doc for eval says "This form is typically used to trap exceptions
more efficiently than the first (see below), while also providing the
benefit of checking the code within BLOCK at compile time." So using
eval just to be able to return is not its "intended purpose".

In fact, the eval version is the one that needs the testing, not the
sub. eval traps fatal terminations, and just about anything can
die(). In this simple case, I think you'd need "use warnings FATAL =>
'numeric';". More realistic examples can die much easier. So you
can't just replace do{...} with eval{...}; you'd have to add proper
error checking and we've recently had a discussion about the edge
cases to worry about.

sub{...}, on the other hand, *is* a straightforward substitution.
Also, for people like me who are used to Lisp and similar languages
that make much more use of small functions created all over, it's a
natural idiom. Also, it has an argument list, so some complexity
could be encapsulated there, allowing the body of the text to refer to
$_[2] or assigning the argument to a variable as one likes.

I don't know how to test efficiency. I've seen a few uses of some
module to run a construct a number of times and report on the time,
but I apparently wasn't using the correct search terms at CPAN.
 
R

Rainer Weikusat

Ben Morrow said:
Quoth (e-mail address removed):
[...]

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.

I infer that "first" means the eval example and "second" means the sub
example.

The notion of "abusing a language construct" in Perl is already a
problematic notion. Perl is the polyperverted abuse bottom of
languages.

The Perl code some people write is 'the polyperverted abuse bottom of
coding', this being enabled by the versatility of the language.
The doc for eval says "This form is typically used to trap exceptions
more efficiently than the first (see below), while also providing the
benefit of checking the code within BLOCK at compile time." So using
eval just to be able to return is not its "intended purpose".

this text continues with

In both forms, the value returned is the value of the last
expression evaluated inside the mini-program; a return
statement may be also used, just as with subroutines.
In fact, the eval version is the one that needs the testing, not the
sub. eval traps fatal terminations, and just about anything can
die(). In this simple case, I think you'd need "use warnings FATAL =>
'numeric';". More realistic examples can die much easier. So you
can't just replace do{...} with eval{...}; you'd have to add proper
error checking and we've recently had a discussion about the edge
cases to worry about.

Almost anything can be turned from a molehill into an insurmountable
moutain by sufficiently wild generalizations. None of this is an issue
for the actual example and there is a single 'edge case' code using
eval for exception handling needs to worry about (There are two more
if some module written by some confused guy is being used. But since
these 'edge cases' have been fixed in Perl 5.14.0, there's little
reason to do so).
sub{...}, on the other hand, *is* a straightforward substitution.
Also, for people like me who are used to Lisp and similar languages
that make much more use of small functions created all over, it's a
natural idiom.

If you want to make it a subroutine then do so. That would be a
sensible choice. Recreating an otherwise invariable anonymous
subroutine every time the code is executed in order to call it once
just to avoid structuring the code as hard as possible isn't.
 
R

Rainer Weikusat

[...]
But only if you expect something to die, and only if it's not acceptable
for the eval to (silently) return an empty list in that case. If you
remember, the most important thing I said was 'if you use raw eval{},
check its return value rather than relying on $@',

In absence of a 'special return value to signal an error condition'
convention the code happens to use, the return value from an eval can
be anything, including 0 or undef. Consequently, this doesn't and
cannot ever work except if the code isn't using exceptions for error
reporting but special return values.

[...]
There'd be little point, since it's a lexical closure. That is, you can
simply refer to outside variables without needing to pass them in.


Benchmark; it's in the core distribution.

I would expect that, if there's any measurable difference, eval is
slower than sub is slower than a plain block is slower than do, simply
because each is doing everything the next does and then a bit more.

As a someone named Daniel Bernstein once quipped "Don't
speculate. Profile". For the extremely simple-minded example below,

----------
use Benchmark;

timethese(-5,
{
eval => sub {
return eval { return 3; };
},

sub => sub {
return sub { return 3; }->();
}});
----------

creating an anonymous throwaway subroutine per invocation is about
half as fast as the eval. This might be different when avoiding this
grotty hack in favor of using a proper, named subroutine (you can
always name it Maeusedreck if you fear that this might render your
code a tad bit to accessible to others ...)
 
T

Tim McDaniel

Concerning
my $result = eval {
$_ % 2 == 0 and return "even";
$_ % 3 == 0 and return "divisible by three";
return "just wrong";
};

Quoth (e-mail address removed):

Why? eval {} can warn perfectly well, if it wants to: in fact, by
converting warnings to errors you would end up hiding warnings that
would otherwise be printed.

I was too elliptical. Sorry. Expanding:

Just about anything can die, and eval will swallow the results. You
might think that this example is so simple that it couldn't possibly
die, and so eval is perfectly harmless in this case. However, you can
even make this die, though the only way I can think of is if to use

use warnings FATAL => 'numeric';

and a non-numeric value of $_.
But only if you expect something to die, and only if it's not
acceptable for the eval to (silently) return an empty list in that
case.

As I demonstrated, surprisingly simple things can die unexpectedly.
Personally, I don't consider something to be a solution if it works
only if nothing dies, but if it doesn't it SILENTLY SUPPRESSES AN
ERROR MESSAGE. When coding it, I'll overlook a way it can die, or
someone will not realize that it's fragile and will break the
assumptions. And if the eval block calls some other function, in
practice it'll be utterly doomed.

In sum: you can't just replace do{...} by eval{...}, and if you try
the result will be fragile, you'll need boilerplate code to propagate
errors, and it will suppress errors or otherwise break if you get it
wrong.
 
R

Rainer Weikusat

Concerning
my $result = eval {
$_ % 2 == 0 and return "even";
$_ % 3 == 0 and return "divisible by three";
return "just wrong";
};



I was too elliptical. Sorry. Expanding:

Just about anything can die, and eval will swallow the results.

Read: It is possible to attach hidden semantics to the code above in
various ways in order to turn eval into an unsuitable solution to the
original problem. Yes, of course. Name a solution of your choice. I
will find a way to break that by violating the invariant conditions it
relies on, probably even without resorting to using, say, dynamically
linked C code to wreak havoc onto some internal data structures.
You might think that this example is so simple that it couldn't
possibly die, and so eval is perfectly harmless in this case.

I think that eval is 'perfectly harmless' in the sense that it will
behave according to its documentation and that you're changing your
problem specification after the fact in order to fabricate reasons
against a possible solution you don't like for 'other reasons'.

[...]
As I demonstrated, surprisingly simple things can die unexpectedly.
Personally, I don't consider something to be a solution if it works
only if nothing dies, but if it doesn't it SILENTLY SUPPRESSES AN
ERROR MESSAGE. When coding it, I'll overlook a way it can die, or
someone will not realize that it's fragile and will break the
assumptions. And if the eval block calls some other function, in
practice it'll be utterly doomed.

In the kind of code you are probably envisioning, where this 'other
function' probably consists of 500 - 1500 lines of code whose exact
raison d'etre was lost with some guy who left the company five years
ago, where generations of successive maintenance programmers have
added patches to the patchwork of patches in order to work around this
or that problem, this is very likely true: As soon as the code calls
another function, only God knows what's going to happen (and if we
can't catch all these weird errors, where to can we hook our next
generation of workarounds).

But that's not a universal problem.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,054
Latest member
LucyCarper

Latest Threads

Top