|
|||
|
tmcd@panix.com (Tim McDaniel) writes:
> In article <871uosoc6n.fsf@sapphire.mobileactivedefense.com >, > Rainer Weikusat <rweikusat@mssgmbh.com> wrote: >>Ben Morrow <ben@morrow.me.uk> writes: >>> Quoth tmcd@panix.com: >> >>[...] >> >>>> 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. |
|
|
||||
|
||||
|
|
|
|||
|
Quoth tmcd@panix.com: > In article <871uosoc6n.fsf@sapphire.mobileactivedefense.com >, > Rainer Weikusat <rweikusat@mssgmbh.com> wrote: > >Ben Morrow <ben@morrow.me.uk> writes: > >> Quoth tmcd@panix.com: > > > >>> 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. I don't know what Rainer meant, but I would have said the reverse: using eval {} as an anon sub rather than a try is not using it for its intended purpose... > 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". ....and I see you agree .> 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';". 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. Even if you went to the trouble of catching and printing them, Perl (5) doesn't have resumable exceptions, so there'd be no way to get back the normal warning behaviour. > 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. 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 $@', which is exactly what you're doing here. > 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. 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. > 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. 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. Ben |
|
|||
|
Ben Morrow <ben@morrow.me.uk> writes:
[...] >> 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. > > 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. > >> 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. > > 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 ...) |
|
|||
|
Concerning
my $result = eval { $_ % 2 == 0 and return "even"; $_ % 3 == 0 and return "divisible by three"; return "just wrong"; }; In article <41tc39-jv12.ln1@anubis.morrow.me.uk>, Ben Morrow <ben@morrow.me.uk> wrote: >Quoth tmcd@panix.com: >> 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';" > >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 $_. >> 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. > >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. -- Tim McDaniel, tmcd@panix.com |
|
|||
|
Quoth tmcd@panix.com: > Concerning > my $result = eval { > $_ % 2 == 0 and return "even"; > $_ % 3 == 0 and return "divisible by three"; > return "just wrong"; > }; > > In article <41tc39-jv12.ln1@anubis.morrow.me.uk>, > Ben Morrow <ben@morrow.me.uk> wrote: > >Quoth tmcd@panix.com: > >> 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';" > > > >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 $_. Oh, I see. Yes, I agree. There are other ways of making this die; the most obvious would be an object in $_ with an overloaded % which dies. Ben |
|
|||
|
tmcd@panix.com (Tim McDaniel) writes:
> Concerning > my $result = eval { > $_ % 2 == 0 and return "even"; > $_ % 3 == 0 and return "divisible by three"; > return "just wrong"; > }; > > In article <41tc39-jv12.ln1@anubis.morrow.me.uk>, > Ben Morrow <ben@morrow.me.uk> wrote: >>Quoth tmcd@panix.com: >>> 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';" >> >>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. 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'. [...] >>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 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. |
|
|
![]() |
| Thread Tools | |
| Display Modes | |
|
|