Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Re: May Thy Closures Be Blessed

by Abigail-II (Bishop)
on Apr 26, 2004 at 20:05 UTC ( #348319=note: print w/replies, xml ) Need Help??


in reply to Re: Re: May Thy Closures Be Blessed
in thread May Thy Closures Be Blessed

They both see basically the same thing wrong with Perl's object system as it is normally used and try to fix it in different ways
While it may solve the problems you see with Perl's OO system, it doesn't solve any of the two biggest problems I see with the system: default data inheritance and no compile time checking of attribute names. Well, I guess one could say it solves the data inheritance problem - but in an extremely awkward way: the superclass defines the attributes of the subclasses. At least, that's how inheritance is happening in your example. Not what I call a real invitation for code reuse.
With Inside-Out Objects, everything starts to look like a hash. With Closure Objects, everything starts to look like a subroutine. A subroutine call is ultimately more flexible than a hash lookup (unless you want to open the can-of-worms that is a tied interface). However, you have to put more work into it.
This sound like false advertisement to me. First, your closure method eventually stores the attributes in a hash. You are just using the closure to give each object its private lexical hash. The closure doesn't provide any additional flexibility - your methods are already doing that. Methods provide the necessary flexibility, regardless of the way of storing attributes - directly in the object, in lexical class level hashes (for Inside Out objects), or in lexical object level hashes (for your closure technique).
IMHO, Inside-Out Objects rely on subtle behavior on Perl's part that make some people feel uneasy. Further, the technique isn't necessarily easy to grasp, even to those that have already mastered Perl's regular object system.
However, anyone having studied Damian's OO book will notice that Inside Out objects are a simple extension on "fly-weight" objects.
They may also appeal more to those with a functional programming background.
Frankly, I fail to see how they connect to 'functional programming'.

Anyway, for a totally different way of doing OO using closures (no 'bless', but with inheritance, AUTOLOADING and SUPER), look at one of my earlier attempts to bypass Perl's OO problems:

package OO::Closures; ###################################################################### +########## # # $Author: abigail $ # # $Date: 1999/08/02 06:06:04 $ # # $Id: Closures.pm,v 1.2 1999/08/02 06:06:04 abigail Exp abigail $ # # $Log: Closures.pm,v $ # Revision 1.2 1999/08/02 06:06:04 abigail # Bug fixes and more efficient use of code (Rick Delaney). # Free and open software copyright/license. # Scalar references as methods (to complement for Eiffel like features +). # CPAN friendly module. # # Revision 1.1 1998/10/01 22:54:57 abigail # Initial revision # # # ###################################################################### +########## use vars qw /$VERSION/; ($VERSION) = '$Revision: 1.2 $' =~ /(\d+.\d+)/; sub import { my $my_package = __PACKAGE__; my $foreign_package = caller; my $my_sub = 'create_object'; shift; my $foreign_sub = @_ ? shift : $my_sub; no strict 'refs'; *{"${foreign_package}::$foreign_sub"} = \&{"${my_package}::$my_sub +"}; } sub croak { require Carp; goto &Carp::croak; } sub create_object { my ($methods, $ISA, $is_base) = @_; sub { my ($method, @args) = @_; my ($call_super, $class); if ($method =~ /^((?:[^:]+|:[^:])*)::(.*)/s) { $class = $1; $method = $2; } if (exists $methods -> {$method} && !defined $class) { return $methods -> {$method} -> (@args) if ref $methods -> {$method} eq 'CODE'; return ${$methods -> {$method}} if ref $methods -> {$method} eq 'SCALAR'; die "Illegal method ($method) in object.\n"; } my @supers; if (defined $class && ($class ne 'SUPER' || exists $ISA -> {$c +lass})) { unless (exists $ISA -> {$class}) { croak "No class ($class) found\n"; } @supers = ($ISA -> {$class}); } else {@supers = values %$ISA} # Go looking for the method in an inherited object. foreach my $super (@supers) { return eval {$super -> ($method => @args)} unless $@; croak $@ unless $@ =~ /^No such method/; } unless ($is_base) { # This is the base object. So, we'll look for AUTOLOAD. return $methods -> {AUTOLOAD} -> ($method => @args) if exi +sts $methods -> {AUTOLOAD} && !defined $class; # Check %ISA for AUTOLOAD. foreach my $super (@supers) { return eval {$super -> (AUTOLOAD => $method, @args)} u +nless $@; croak $@ unless $@ =~ /^No such method/; } } croak "No such method ($method) found"; } } 1; __END__ =pod =head1 NAME OO::Closures - Object Oriented Programming using Closures. =head1 SYNOPSIS use OO::Closures; sub new { my (%methods, %ISA, $self); $self = create_object (\%methods, \%ISA, !@_); ... $self; } =head1 DESCRIPTION This package gives you a way to use Object Oriented programming using Closures, including multiple inheritance, C<SUPER::> and AUTOLOADing. To create the object, call the function C<create_object> with three arguments, a reference to a hash containing the methods of the object, a reference to a hash containing the inherited objects, and a flag determining whether the just created object is the base object or not. This latter flag is important when it comes to trying C<AUTOLOAD> after not finding a method. C<create_object> returns a closure which will act as the new object. Here is an example of the usage: use OO::Closures; sub dice { my (%methods, %ISA, $self); $self = create_object (\%methods, \%ISA, !@_); my $faces = 6; $methods {set} = sub {$faces = shift;}; $methods {roll} = sub {1 + int rand $faces}; $self; } It is a simple object representing a die, with 2 methods, C<set>, to s +et the number of faces, and C<roll>, to roll the die. It does not inherit anything. To make a roll on a 10 sided die, use: (my $die = dice) -> (set => 10); print $die -> ('roll'); Note that since the objects are closures, method names are the first arguments of the calls. =head1 OBJECT VARIABLES One can make object variables available to the outside world as well. Just like in Eiffel, for an outsider this will be indistinguishable from accessing a argumentless method. (However, in a Perlish fashion, we will actually allow arguments in the call). To do so, instead of putting a code reference in the %methods hash, put a reference to a scalar in the hash. Note that to the outside world, no more than read only access to the variable is given. Here is an example: sub dice { my (%methods, %ISA, $self); $self = create_object (\%methods, \%ISA, !@_); my $faces = 6; $methods {set} = sub {$faces = shift;}; $methods {roll} = sub {1 + int rand $faces}; $methods {faces} = \$faces; $self; } (my $die = dice) -> (set => 10); print $die -> ('faces'); This will print C<10>. =head1 INHERITANCE To use inheritance, we need to set the C<%ISA> hash. We also need to pass ourselves to the classes we inherited, so an inherited class can find the base object. (This is similar to the first argument of the constructor when using regular objects). Here is an example that implements multi dice, by subclassing C<dice>. We will also give C<dice> a method C<print_faces> that prints the numb +er of faces and returns the object. use OO::Closures; sub dice { my (%methods, %ISA, $self); $self = create_object (\%methods, \%ISA, !@_); my $this_object = shift || $self; my $faces = 6; $methods {set} = sub {$faces = shift}; $methods {roll} = sub {1 + int rand $faces}; $methods {print_faces} = sub {print "Faces: $faces\n"; $this_o +bject}; $self; } sub multi_dice { my (%methods, %ISA, $self); $self = create_object (\%methods, \%ISA, !@_); my $this_object = shift || $self; %ISA = (dice => dice $this_object); my $amount = 1; $methods {amount} = sub {$amount = shift}; $methods {roll} = sub { my $sum = 0; foreach (1 .. $amount) {$sum += $self -> ('dice::roll')} $sum; }; $self; } my $die = multi_dice; $die -> (set => 7); $die -> (amount => 4); print $die -> ('print_faces') -> ('roll'), "\n"; __END__ Notice the line C<my $this_object = shift || $self;>. That will make C<$this_object> contain the base object, unlike C<$self> which is the instance of the current class. The class C<dice> is subclassed in C<multi_dice> by calling C<dice> with an extra argument, the base object. Now it's known that C<dice> is subclassed, and looking for C<AUTOLOAD> if it cannot find the requested method should not happen; that will be triggered by the base object. Inherited classes are named, but they are named by the inheriter, not the inheritee. This allows you to inherit the same class multiple times, and getting separate data and method space for it. When searching for methods in the inheritance tree, no order will be garanteed. If you subclass multiple classes defining the methods with the same name, it's better to mask those methods and explicitely redirect the call to the class you want it to handle. You can call a method by prepending its class name(s); just like regular objects. Inherited classes are stored in the C<%ISA> hash, but since this varia +ble is private to the object, each object can have its own inheritance structure. If you change a class, existing objects of the class will n +ot be modified. The pseudo class 'SUPER::' works the same way as regular objects do, except that it works the right way. It will resolve 'SUPER::' dependin +g on the inherited classes of the object the method is called in; not on the C<@ISA> of the package the call is made from. =head1 C<use OO::Closures;> By default, the module C<OO::Closures> exports the function C<create_o +bject>. If you want this function to be known by another name, give that name as an argument to the C<use> statement. use OO::Closure 'other_name'; Now you create objects with C<other_name (\%methods, \%ISA, !@_);> =head1 BUGS This documentation uses the word 'class' in cases where it's not reall +y a class in the sense of the usual object oriented way. Mark-Jason Domi +nus calls this I<class-less> object orientism. =head1 HISTORY $Log: Closures.pm,v $ Revision 1.2 1999/08/02 06:06:04 abigail Bug fixes and more efficient use of code (Rick Delaney). Free and open software copyright/license. Scalar references as methods (to complement for Eiffel like featur +es). CPAN friendly module. Revision 1.1 1998/10/01 22:54:57 abigail Initial revision =head1 AUTHOR This package was written by Abigail, I<abigail@delanet.com>. =head1 COPYRIGHT and LICENSE This code is copyright 1998, 1999 by Abigail. This code is free and open software. You may use, copy, modify, distri +bute, and sell this program (and any modified variants) in any way you wish, provided you do not restrict others from doing the same. =cut

Abigail

Replies are listed 'Best First'.
Re: Re: May Thy Closures Be Blessed
by hardburn (Abbot) on Apr 26, 2004 at 20:22 UTC

    I guess one could say it solves the data inheritance problem - but in an extremely awkward way: the superclass defines the attributes of the subclasses. At least, that's how inheritance is happening in your example.

    Yes, this is something I need to think about more. I think it's possible, but the specifics haven't been worked out. (Along with a lot of other ideas).

    Much of the power of Closure Objects will depend on how much work you're willing to put into the closure.

    First, your closure method eventually stores the attributes in a hash. You are just using the closure to give each object its private lexical hash.

    That is how it is currently implemented, but there's no reason why it has to be limited to some access controls and a hash lookup. The technique enforces the "eat your own dogfood" rule (i.e., if you define accessors/mutators on class data, use them even inside the class itself). It allows for a lot of things that would otherwise have to be done by a tied interface.

    Frankly, I fail to see how they connect to 'functional programming'.

    Closures come straight out of functional programming. Plus I like ideas that combine concepts that are normally thought of as completely seperate.

    ----
    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

      That is how it is currently implemented, but there's no reason why it has to be limited to some access controls and a hash lookup.
      Oh, sure, but that's isn't anymore true for your closure solution as it is for more traditional methods (IO::* for instance uses blessed handles), or for Inside Out objects.

      Abigail

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://348319]
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (3)
As of 2017-09-20 23:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    During the recent solar eclipse, I:









    Results (241 votes). Check out past polls.

    Notices?