Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: (On Speed Diff, etc.) Closure on Closures (beta)

by chunlou (Curate)
on Jun 25, 2003 at 23:12 UTC ( #269051=note: print w/ replies, xml ) Need Help??


in reply to Closure on Closures

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.


Comment on Re: (On Speed Diff, etc.) Closure on Closures (beta)
Select or Download Code
Re: Re: (On Speed Diff, etc.) Closure on Closures (beta)
by diotalevi (Canon) on Jun 25, 2003 at 23:27 UTC

    Your object method calls are slower because the add on ISA lookup/maintenance (just because it is a method), a variable dereference and a hash lookup. The base closure is just a function and postincrement. It makes sense for one to be slower, both perform the same task, one has to do a bit more janitorial work. Consider though, that your closure might be heavier in memory requirements. Does any of this really matter? Probably not. This is not the sort of thing I worry about. Generally my decision on how to organize my data is driven by other requirements than method call overhead. In fact, it always is. Except for once when I was doing a really, really big batch job. Then I used plain functions.

      In short, (in Perl at least) closure is faster than object due to less dereferencing overhead but could be slower due to larger memory requirement? (In the test, the edge of closure over object was diminishing and even reversed when the tasks were getting more "heavy-duty.")

      Different question, if I may. I want to focuse on Design, as opposed to Programming.

      How would you characterize and procedurize a "functional design process" (if there is such a thing). Like a user manual on Design: the Functional (or Closure) Way.

      I hope it's self-evident that design and programming are not always an integrated process. Many people program in Java the imperative or procedural way pretty much, nothing so OO about the design of the codes.

      On the other hand, a design framework could be applied more generally even when the tools are not integrated with it. Like, OO Design is a good framework to guide your thinking process to characterize and generalize some human verbal requirements into classes. For instance, you could think of a Webpage being a class and the stored procedure associated with it being methods, even though HTML, SQL, etc are not OO.

      Besides the characterization of the process, what would be the examples constrasting the closure/functional of design versus the other whatever ways, along with the advantages--design-wise or conceptual--such as being more succinct, flexible, insightful, etc? (Like, in physics, some people do things the group-theoretic way vs. the good old calculcus way; in economics, game-theoretic vs others. They produce different insights into the same problems and sometimes even different conclusions.)

      Take the scripts in the test as examples. Closure counter certainly looks like a cleaner design (or model); object is probably an overkill in this case. With the Turtle, if we need more "methods," object seems like a natural way to go. With directory iterator, I really have no preference towards either.

      Thanks.

      _______________________
      Update: As a afterthought, maybe some examples on translating some existing OO design/code into closure one (if applicable) would be pedagogically more instructive? Since I suppose more people can relate to OO than closure.

        In short, (in Perl at least) closure is faster than object due to less dereferencing overhead but could be slower due to larger memory requirement? (In the test, the edge of closure over object was diminishing and even reversed when the tasks were getting more "heavy-duty.")
        No, larger just means larger (unless you get so large you swap). Heavy use of closures would make me at least consider memory usage.

        How would you characterize and procedurize a "functional design process" (if there is such a thing). Like a user manual on Design: the Functional (or Closure) Way.
        I read two distinct thoughts in the rest of your question. Design by documentation and programming style. The programming style question is likely best answered by pointing you to Why I like functional programming. My own coding style is largely procedural for the smaller cases, object oriented for the larger picture with plenty of functional goodness. Most of my exposure to logical programming comes from SQL and XSLT (which I adore). The point is that for common, every day programming there is a lot to be gained by being flexible and drawing from many disciplines.

        It lets me get my work done and done right.

        As for programming by documentation, I'm playing with that right now. For the project I'm on I've been writing the documentation for processes that the code implements and then just like mortar, filling actual code in after the fact. So far its a dream. I think other people call this ``Design by Contract'' but I couldn't swear to that. In fact, there's a module Class::Contract which purports to help you write code that way (though if you read just the documentation you won't understand DoC).

Re: Re: (On Speed Diff, etc.) Closure on Closures (beta)
by yosefm (Friar) on Jun 26, 2003 at 08:48 UTC
    I've seen objects actually implemented with a closure (like in this tutorial), as a way to give your objects a 'private/public' distinction. I wonder what would that do to performance...
      Right, that's a very clever use of closure. Maybe I didn't read enough, but I don't see such technique being used too often. It's good for avoiding more deliberate tempering with variables in a package. Isn't the usual lexical scope good enough to avoid accidental clashes between things?
Re: (On Speed Diff, etc.) Closure on Closures (beta)
by Abigail-II (Bishop) on Jun 26, 2003 at 15:36 UTC
    Object is data wrapped in methods.
    Closure is subroutine wrapped in data

    It's actually the other way around. An object exposes data, a closure exposes code. As Tom C. used to say:

    An object is data that knows which code acts on it.
    A closure is code that knows which data to act on.

    Abigail

      And you should know, Abigail!!!

      Actually, as Abigail just did (go quickly and grab Closures-1.2 in http://cpan.org/modules/by-authors/id/ABIGAIL/), you can construct a whole object system on top the closures (it's been done some dozen times in different ways in Scheme, where basically everything is a closure).

      A simpler example, consider this code:

      use strict; use warnings; sub Person { my %self = (); my @data = ( qw/name age/ ); my %mem = (); sub { for my $sub ( @data ) { $self{$sub} = sub { $mem{$sub} = $_[0] if @_; return $mem{$sub} } } return \&{$self{$_[0]}} if defined $self{$_[0]}; die "Method '$_[0]' unknown to class Person"; } }
      We create a closure which exposes the interface of the class and holds both the methods (in %self) and data (in %mem, from memory). Please, note that it's impossible to read or write to the values held in %mem or %self (ie, modify the state of the object or modify its interface) directly, only through the interface exposed, which is very hygienic in itself. Using this is easy (if somewhat not very idiomatic):
      # creating object my $john = Person; # initializing values $john->('name')->('John'); $john->('age')->(23); # working with object print "I'm ", $john->('name')->(), " and I am ", $john->('age')->(), " years-old, but my birthday is in just 2 seconds\n"; sleep( 2 ); $john->('age')->(24); print "Happy B-Day, ", $john->('name')->(), "!!!\n"; print "It's ", $john->('age')->(), " years-old already!!!\n"; print $john->('address')->(); __END__

      Output is as expected (text between []'s is mine):

      I'm John and I am 23 years-old, but my birthday is in just 2 seconds [two seconds pass...] Happy B-Day, John!!! It's 24 years-old already!!! Uncaught exception from user code: Method 'address' unknown to class Person at closure.pl line 18 +. main::__ANON__('address') called at closure.pl line 40

      I am sure it's not too hard being able to force the dereference of the methods when you are on getter mode so instead of writing $john->('age')->() you can just say $john->('age') (as Abigail has in Closures-1.2). Probably an additional line checking the number of arguments passed to the method.

      Except being the state held in the closures, this is all pure functional code; functional programming rules, yeah :)

      best regards,
      david

      --
      our $Perl6 is Fantastic;

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://269051]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (11)
As of 2014-08-22 20:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (164 votes), past polls