I read somewhere that said something like:
Object is data wrapped in methods.
Closure is subroutine wrapped in data.
Instead of talking about the philosophical differences first, I ran some benchmarks to compare closure and object, just for the fun of it. I have the following three results:
Counter:
use strict;
use warnings;
use Benchmark qw(cmpthese);
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub closure_counter {
my $count = shift;
return sub { $count++ };
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{package obj_counter;
sub new {bless {count=>$_[1]};}
sub count {return ($_[0]->{count})++}
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $obj_counter = obj_counter->new(3);
my $closure_counter = closure_counter(3);
cmpthese(1000,
{
obj_counter=>sub{$obj_counter->count()for 1..1000;},
closure_counter=>sub{$closure_counter->()for 1..1000;}
}
);
__END__
Benchmark: timing 1000 iterations of closure_counter, obj_counter...
closure_counter: 2 wallclock secs ( 2.58 usr + 0.00 sys = 2.58 CPU)
+ @ 387.60/s (n=1000)
obj_counter: 5 wallclock secs ( 5.22 usr + 0.00 sys = 5.22 CPU) @ 1
+91.57/s (n=1000)
Rate obj_counter closure_counter
obj_counter 192/s -- -51%
closure_counter 388/s 102% --
Directory Iterator:
use strict;
use warnings;
use Benchmark qw(cmpthese);
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
use IO::Dir;
sub closure_dir_iter {
my $dir = IO::Dir->new(shift);
return sub {
my $fl = $dir->read();
$dir->rewind() unless defined $fl;
return $fl;
};
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{package obj_dir_iter;
use IO::Dir;
sub new {bless {dir=>IO::Dir->new($_[1])};}
sub iter {
my $fl = $_[0]->{dir}->read();
$_[0]->{dir}->rewind() unless defined $fl;
return $fl;
}
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $obj_dir_iter = obj_dir_iter->new( "." );
my $closure_dir_iter = closure_dir_iter( "." );
cmpthese(500,
{
obj_dir_iter=>sub{while(defined(my $f = $obj_dir_iter->iter())
+){print "$f\n";}},
closure_dir_iter=>sub{while(defined(my $f = $closure_dir_iter-
+>())){print "$f\n";}}
}
);
__END__
Benchmark: timing 2000 iterations of closure_dir_iter, obj_dir_iter...
obj_dir_iter: 1 wallclock secs ( 1.20 usr + 0.00 sys = 1.20 CPU) @
+1666.67/s (n=2000)
closure_dir_iter: 1 wallclock secs ( 1.10 usr + 0.00 sys = 1.10 CPU
+) @ 1818.18/s (n=2000)
Rate obj_dir_iter closure_dir_iter
obj_dir_iter 1667/s -- -8%
closure_dir_iter 1818/s 9% --
Turtle Graph (drawing Koch curve)
:
use strict;
use warnings;
use Benchmark qw(cmpthese);
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
use constant PI => 3.14159265359;
sub closure_turtle {
my ($h, $xy) = (0, [[0],[0]]); # h = heading (0 - north, 90 -
+east, etc)
return sub {
$h = $h + (shift || 0); # accumulative turns in degree
my $d = shift || 0; # distance
$xy->[0][scalar(@{$xy->[0]})] = $d*sin(PI*$h/180) + $xy->[0][$
+#{@{$xy->[0]}}];
$xy->[1][scalar(@{$xy->[1]})] = $d*cos(PI*$h/180) + $xy->[1][$
+#{@{$xy->[1]}}];
return $xy;
};
}
sub closure_koch {
my ($turtle, $d, $level) = @_ ;
if ($level==0) {$turtle->(0,$d); return 1;}
$turtle->( 0,0); closure_koch($turtle,$d/3,$level-1);
$turtle->(-60,0); closure_koch($turtle,$d/3,$level-1);
$turtle->(120,0); closure_koch($turtle,$d/3,$level-1);
$turtle->(-60,0); closure_koch($turtle,$d/3,$level-1);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{package obj_turtle;
use constant PI => 3.14159265359;
sub new {return bless({h=>0,xy=>[[0],[0]]});}
sub rt {$_[0]->{h}=$_[0]->{h}+$_[1];} # right turn by x
+ degrees
sub fd { # forward by x poi
+nts
my ($h, $xy, $d) = ($_[0]->{h}, $_[0]->{xy}, $_[1]);
$xy->[0][scalar(@{$xy->[0]})] = $d*sin(PI*$h/180) + $xy->[0][$
+#{@{$xy->[0]}}];
$xy->[1][scalar(@{$xy->[1]})] = $d*cos(PI*$h/180) + $xy->[1][$
+#{@{$xy->[1]}}];
$_[0]->{xy} = $xy; return $xy;
}
}
sub obj_koch {
my ($turtle, $d, $level) = @_ ;
if ($level==0) {$turtle->fd($d); return 1;}
$turtle->rt( 0); obj_koch($turtle, $d/3,$level-1);
$turtle->rt(-60); obj_koch($turtle, $d/3,$level-1);
$turtle->rt(120); obj_koch($turtle, $d/3,$level-1);
$turtle->rt(-60); obj_koch($turtle, $d/3,$level-1);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $obj_turtle = obj_turtle->new();
my $closure_turtle = closure_turtle();
cmpthese(100,
{
obj_turtle=>sub{for(0..2){$obj_turtle->rt(120); obj_koch($obj_
+turtle,170,4);}},
closure_turtle=>sub{for(0..2){$closure_turtle->(120, 0); closu
+re_koch($closure_turtle,170,4);}}
}
);
__END__
Benchmark: timing 100 iterations of closure_turtle, obj_turtle...
closure_turtle: 6 wallclock secs ( 6.49 usr + 0.00 sys = 6.49 CPU)
+@ 15.41/s (n=100)
obj_turtle: 5 wallclock secs ( 4.99 usr + 0.00 sys = 4.99 CPU) @ 20
+.04/s (n=100)
Rate closure_turtle obj_turtle
closure_turtle 15.4/s -- -23%
obj_turtle 20.0/s 30% --
Closure counter beat object's by ~100%; Closure directory iterator beat object's by ~10%; but object turtle beat closure's by ~30%.
Why the difference?
Going from empirical to completely academic and philosophical, closure has pretty good application in lambda calculus. Consider the following code (we use "%" place of "lambda"):
# IF = %b.(%x.(%y.(b x) y))
$IF = sub {
my $b = shift;
sub {
my $x = shift;
sub {
my $y = shift;
$b->($x)->($y);
}
}
}
# TRUE = %x.(%y.x)
$TRUE = sub {
my $x = shift;
sub {
my $y = shift;
$x;
}
}
# FALSE = %x.(%y.y)
$FALSE = sub {
my $x = shift;
sub {
my $y = shift;
$y;
}
}
print $IF->($TRUE)->("then")->("else"); # prints "then"
print $IF->($FALSE)->("then")->("else"); # prints "else"
To verify algebraically
(IF TRUE A B)
observe that
(%b.%x.%y.(b x y) %a.%b.a A B)
(%x.%y.(%a.%b.a x y) A B)
(%y.(%a.%b.a A y) B)
(%a.%b.a A B)
(%b.A B)
A
Likewise, (IF FALSE A B) gives us B. (Full discussion can be found
here.)
Is it a big deal? Well, set theory and mathematical logic may look naively useless (which after all only give us Incompleteness Theorem); or number theory or group/fields theory may seem trivial (where only comes some obscure Elliptic Curves for us to make highly unbreakable encryption).
Lisp, considered a functional language, where closure's considered a major technique, is often used to implement A.I. stuff--somewhere along the line, there come RegEx, Mathematica, and taken-for-granted stuff like that.
Is closure (and functional programming) for everyone? Not when a third of the bookshelves in Computer section in the major bookstores are stuffed with Java and OOP. It's hard to be Func-ie when almost everyone else is Ob-ing.
But then, if somebody could find good use of group theory, somebody could surely find meaningful application of closure and functional programming. It's just a matter of creativity and practice.