Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Re: May Thy Closures Be Blessed

by dragonchild (Archbishop)
on Apr 26, 2004 at 15:09 UTC ( #348195=note: print w/replies, xml ) Need Help??


in reply to May Thy Closures Be Blessed

An interesting discussion would be the differences between this and Inside-out classes. Both are based on lexical scoping to enforce encapsulation. What are the benefits/drawbacks of either method?

------
We are the carpenters and bricklayers of the Information Age.

Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

Replies are listed 'Best First'.
Re^2: May Thy Closures Be Blessed
by adrianh (Chancellor) on Apr 26, 2004 at 15:34 UTC
    An interesting discussion would be the differences between this and Inside-out classes. Both are based on lexical scoping to enforce encapsulation. What are the benefits/drawbacks of either method?

    Off the top of my head:

    • You don't have to mess around with DESTROY with the closure based method, which makes maintenance easier.
    • Inside out objects are more space efficient than closures.
    • Accessing object state is a subroutine call with closure objects, and a hash access with inside-out objects. The latter will be a fair bit faster.
    • You can subclass an existing non-inside-out object and add inside-out based state to the objects in the subclass. You can't do this with without delegation with closures.
    • You need a bit more mechanism than the proposal hardburn showed above to get separate namespaces for state in different subclasses. This comes for free with inside-out.
    • You don't get compile-time checking of your instance variable naming with closures.
    • added 16:58 GMT+1:Inside out instance variables are scoped to the class they are declared in. With the mechanism outlined in the OP instance variables can also be accessed in subclasses.

      Inside Out objects are faster than the closure based - but compared to "traditional" objects, the difference is small:
      #!/usr/bin/perl use strict; use warnings; use Benchmark qw /cmpthese/; use Carp; package Class_Closure; sub new { my $class = shift; my %args = @_; my %field = ( name => $args {name} || "abigail", colour => $args {colour} || "pink", age => $args {age} || 100, class => $class, ); bless sub { my $name = shift; my ($package, $filename, $line) = caller; die "Attempt to access private class data " . "for $field{class} at $filename line $line\n" unless UNIVERSAL::isa ($package => __PACKAGE__); die "No such field '$name' at $filename line $line\n" unless exists $field{$name}; die "You can't change the class name at $filename line $line\n +" if $name eq 'class'; $field {$name} = shift if @_; $field {$name} } => $class; } sub name {my $self = shift; $self -> (name => @_)} sub colour {my $self = shift; $self -> (colour => @_)} sub age {my $self = shift; $self -> (age => @_)} sub format { my $self = shift; join " " => $self -> ('name'), $self -> ('colour'), $self -> ('age +'); } package Class_Inside_Out; my %name; my %colour; my %age; sub new { my $key = bless \(my $dummy) => shift; my %args = @_; $name {$key} = $args {name} || "abigail"; $colour {$key} = $args {colour} || "pink"; $age {$key} = $args {age} || 100; $key; } sub name { my $key = shift; $name {$key} = shift if @_; $name {$key}; } sub colour { my $key = shift; $colour {$key} = shift if @_; $colour {$key}; } sub age { my $key = shift; $age {$key} = shift if @_; $age {$key}; } sub format { my $key = shift; join " " => $name {$key}, $colour {$key}, $age {$key}; } package Class_Traditional; sub new { my $class = shift; my %args = @_; bless {name => $args {name} || "abigail", colour => $args {colour} || "pink", age => $args {age} || 100} => $class; } sub name { my $self = shift; $self -> {name} = shift if @_; $self -> {name} } sub colour { my $self = shift; $self -> {colour} = shift if @_; $self -> {colour} } sub age { my $self = shift; $self -> {age} = shift if @_; $self -> {age} } sub format { my $self = shift; join " " => @$self {qw /name colour age/}; } package main; our $obj_c = Class_Closure -> new; our $obj_i = Class_Inside_Out -> new; our $obj_t = Class_Traditional -> new; our @names = ("Larry Wall", "Damian Conway", "Nicholas Clark", "Gurusamy Sarathy", "Chip Salzenberg", "Rafael Garcia-Suarez"); our @colours = qw /red green blue white yellow orange brown purple vio +let/; # # Test. # my $name = $names [rand @names]; my $colour = $colours [rand @colours]; my $age = 1 + int rand 100; foreach my $i ([obj_c => $obj_c], [obj_i => $obj_i], [obj_t => $obj_t] +) { $i -> [1] -> name ($name); $i -> [1] -> colour ($colour); $i -> [1] -> age ($age); die $i -> [0] unless "$name $colour $age" eq $i -> [1] -> format; } our $dummy; cmpthese -1 => { closure => 'foreach my $n (@names) { foreach my $c (@colours) { my $age = 1 + int rand 100; $obj_c -> name ($n); $obj_c -> colour ($c); $obj_c -> age ($age); $dummy = $obj_c -> format; } }', inside_out => 'foreach my $n (@names) { foreach my $c (@colours) { my $age = 1 + int rand 100; $obj_i -> name ($n); $obj_i -> colour ($c); $obj_i -> age ($age); $dummy = $obj_i -> format; } }', traditional => 'foreach my $n (@names) { foreach my $c (@colours) { my $age = 1 + int rand 100; $obj_t -> name ($n); $obj_t -> colour ($c); $obj_t -> age ($age); $dummy = $obj_t -> format; } }', }; __END__ Rate closure inside_out traditional closure 355/s -- -25% -75% inside_out 473/s 33% -- -67% traditional 1436/s 304% 203% --

      Abigail

        This is probably due to inefficient stringification of the object reference. Maybe the stringified value should be put into the PV portion and POK should be set on while doing Perl_sv_2pv_flags ?
Re: May Thy Closures Be Blessed
by Abigail-II (Bishop) on Apr 26, 2004 at 16:46 UTC
    Both are based on lexical scoping to enforce encapsulation.
    But an entire different kind of encapsulation! The closure based method as outline does data inheritance. By default, and you'll need to do work to prevent it. Inside Out objects don't do data inheritance (except if you define your subclass in the same lexical scope as your superclass - but you probably have good reasons to do so).

    Furthermore, Inside Out objects don't require their superclass, or subclasses, to cooperate. With the Inside Out technique, you can subclass anything, including a closure, without interfering. Even if the superclass changes its implementation, you're safe. The closure based strategy only works if the entire inheritance tree uses the same closure.

    Inside Objects are about maximizing freedom - it does not impose, and it does not require. ;-)

    Abigail

      I wasn't at all surprised at the many comparisons to Inside-Out Objects that popped up in this thread. 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. I must admit that the closure method didn't do some things as well as I thought it would when the idea first popped into my head a few days ago.

      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.

      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. Closures aren't necessarily easy, but I suspect they will be easier to think about than the Inside-Out technique. They may also appeal more to those with a functional programming background.

      Yes, Closure Objects do impose that the heirarchracy all use the closure technique. I think it's a net gain for freedom, even over Inside-Out Objects. It will ultimately depend on what you're trying to accomplish, though I suspect either technique will solve your problem if you put enough effort into it. Further, which technique is easier for your project may not be clear from the start.

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

      Note: All code is untested, unless otherwise stated

        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:

        I predict that the closure method is worthy of the thought, but not of using. Here is my reasoning:
        1. The fact that I like closures, does not argue that I should want to always use closures. The key difference between OO and using closures heavily is in how you organize code, not how you implement things. Once I've chosen to organize things in an OO fashion, then I'm going to find my code growing in ways that OO code does, and OO design principles will be applicable.
        2. One of the big OO principles is the open-closed principle. Modules should be open for extension and closed for modification. That is, you should be able to leave a base class alone and usefully extend it by subclassing. This ideal is more often honoured in the breach than the observation, but it is worthwhile trying to think in terms of designs that can work that way.
        3. The open-closed principle says that the closure approach is worse than alternatives. You can't make unanticipated changes without either changing the base class implementation, or else working around the implementation with something like inside-out objects.
        4. Personally I value having Storable work more than I do ways of enforcing encapsulation. Therefore I don't have problems with the traditional hash approach.
      Furthermore, Inside Out objects don't require their superclass, or subclasses, to cooperate.

      They do need to co-operate over DESTROY.

        They do need to co-operate over DESTROY.
        Yes, but not more than any other form of inheritance does. If you subclass a class, and define a DESTROY method in your subclass, without calling DESTROY in the superclass, things are likely to break. Your DESTROY method should always contain something like (for single inheritance):
        $self -> SUPER::DESTROY if $ISA [0] -> can ("DESTROY");
        regardless whether you use Inside-Out objects or not.

        Abigail

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (7)
As of 2018-04-23 12:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?