This also works nicely with state built-in of 5.10+ for tighter encapsulation, compile-time evaluation of hash:
>perl -wMstrict -le
"use 5.010;
;;
sub expr {
state $op_func = {
'+' => sub { $_[0] + $_[1] },
'-' => sub { $_[0] - $_[1] },
'*' => sub { $_[0] * $_[1] },
'/' => sub { $_[0] / $_[1] },
};
;;
my ($lhs, $op, $rhs) = split ' ', shift;
return $op_func->{$op}->($lhs, $rhs);
}
;;
for my $op (qw(+ - * /)) {
my $exp = qq{4 $op 5};
print $exp, ' = ', expr($exp);
}
"
4 + 5 = 9
4 - 5 = -1
4 * 5 = 20
4 / 5 = 0.8
| [reply] [d/l] |
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% --
| [reply] [d/l] [select] |
... 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...!
| [reply] [d/l] [select] |
Thanks a lot to everyone ... :) | [reply] |