in reply to Re^2: Array of operators ...
in thread Array of operators ...

For my own code (I'm running 5.18.1), I probably would have used state as you've done there: your post got a ++ from me. However, unless the question specifically involves a more recent Perl, or I know the person I'm responding to is familiar with 5.10.0+, I find providing a pre-5.10 (or, more strict, an any Perl5) solution to be a lot less hassle: that would be non-virtuous laziness on my part. :-)

While there may be some subtlety I'm missing, which I'm more than happy to learn about, I really don't see any "tighter encapsulation". In both cases, the hash is only evaluated once and only visible by the expr subroutine. From a visual perspective, the hash is clearly more closely associated with the subroutine: in fact, that would be one of my reasons for preferring it.

There's another issue which I'd forgotten about until I ran a few tests. You can't simply substitute my with state in my %op_func = (...);. If you try this, you get a compilation error:

"Initialization of state variables in list context currently forbidden"

So, you need to code state $op_func = {...}; as you have here. Having to dereference $op_func incurs a minimal overhead which, in general, wouldn't prevent me from using it unless efficiency was of particular concern. I found the %op_func solution to be consistently 2% faster than the $op_func solution:

$ perl -Mstrict -Mwarnings -E ' use Benchmark qw{cmpthese}; { my %op_func1 = ( "+" => sub { $_[0] + $_[1] }, "-" => sub { $_[0] - $_[1] }, "*" => sub { $_[0] * $_[1] }, "/" => sub { $_[0] / $_[1] }, ); sub expr1 { my ($lhs, $op, $rhs) = split " ", shift; $op_func1{$op}->($lhs, $rhs); } } sub expr2 { state $op_func2 = { "+" => sub { $_[0] + $_[1] }, "-" => sub { $_[0] - $_[1] }, "*" => sub { $_[0] * $_[1] }, "/" => sub { $_[0] / $_[1] }, }; my ($lhs, $op, $rhs) = split " ", shift; $op_func2->{$op}->($lhs, $rhs); } my @ops = ("+","-","*","/"); cmpthese(-1, { with_my => sub { expr1("4 $_ 5") for @ops }, with_state => sub { expr2("4 $_ 5") for @ops }, }); ' Rate with_state with_my with_state 112733/s -- -2% with_my 114840/s 2% --

-- Ken

Replies are listed 'Best First'.
Re^4: Array of operators ...
by AnomalousMonk (Archbishop) on Sep 14, 2013 at 11:38 UTC
    ... tighter encapsulation ...

    I doubt any of this will be new to you, but just to clarify my thoughts for myself, I suppose what I really had in mind was actually two separate and disparate effects:

    • If a 'private' closure is not further encapsulated in a use-d module, pesky order-of-evaluation effects can manifest:
      >perl -wMstrict -le "print 'before: ', defined S() ? S() : 'undefined'; ;; { my $x = 42; ;; sub S { return $x; } } ;; print 'after: ', defined S() ? S() : 'undefined'; " before: undefined after: 42
    • If such a closure is encapsulated in a used-d module, the order-of-evaluation problems go away (assuming you're not doing any fancy-schmancy CTFE in the module, I think this is reliably true), but the 'tighter' aspect, strictly speaking, then kicks in: a state variable really is private to the function in which it's defined, but 'private' closures allow the sharing of access to variables among several functions — in itself, a highly useful property in certain circumstances!
      >perl -wMstrict -le "print M::get_x(); ;; use M; ;; print M::get_x(); " 42 42
      Where M.pm is:
      package M; { my $x = 42; sub get_x { return $x; } sub set_x { return $x = $_[0]; } } my $y = 1729; sub cannot_access_x { return $y; } 1;

    Oh, and:

    ... consistently 2% faster ...

    I'm not sure I really believe in a 2% performance difference reported by Benchmark, but as long as it's you...!

      Thanks for the feedback. I agree with your points. I have been tripped up by that before/after effect in the past and needed to reorder my code to deal with it.

      I had expected a minimal overhead and 2% reflects this. Obviously, being an integer percentage, it doesn't exhibit a huge amount of precision. I usually run Benchmark several times, throw away any outliers and report an average or range. In this instance, all 3 or 4 runs produced 2%. It indicates an overhead but I wouldn't be setting my watch by it. :-)

      -- Ken