Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Functional Inside Out Closure Objects

by fletcher_the_dog (Friar)
on May 20, 2004 at 15:01 UTC ( #354972=perlquestion: print w/ replies, xml ) Need Help??
fletcher_the_dog has asked for the wisdom of the Perl Monks concerning the following question:

I, like many people (See the number of Class::* modules on CPAN), have sought to make oop easier in perl. After looking at many different ideas including closure objects, inside-out objects, and Class::Struct I came up with my own way of creating objects that looks something like this:
#!/usr/bin/perl package Foo; use Class::FIOC; use strict; my $nextid=1; sub new { my $class = shift; my %args = @_; my $first = $args{first} || "unknown"; my $surname = $args{surname} || "unknown"; my $id = $nextid++; methods { fullname=>sub { return "$first $surname"; }, surname=>sub{ if (@_==2) { $surname = pop; } return $surname; }, first=>sub { if (@_==2) { $first=pop; } return $first; }, id=>sub{ return $id; }, debug=>sub{ print "First = $first\nSurname=$surname\nID=$id\n\n"; } }; } my $foo = new Foo(first=>"Fred",surname=>"Flintstone"); print $foo->first."\n"; print $foo->surname."\n"; print $foo->id."\n"; print $foo->fullname."\n"; $foo->debug; $foo = new Foo(first=>"Barney",surname=>"Rubble"); print $foo->first."\n"; print $foo->surname."\n"; print $foo->id."\n"; print $foo->fullname."\n"; $foo->debug;
My goals were to make the syntax easy without using source filters, to ensure encapsulation, to allow inheritance, to make access to instance variables inside of methods easy, and to have as little overhead as possible. I call it a functional object because the actually object can be thought of a collection of functions that share some lexical variables, more than a collection of variables that you can call methods on. I call it an inside out object because if you look at the source code for FIOC.pm below, you can see that the functions are actually stored in a hash outside of object and the object is just a key to those hashes like in Abigail-II's inside out objects. It is called a closure object because the functions that make up an object are all closures.
I think I made my goal of making the syntax simple. Class::FIOC imports just one method "methods" which takes a hash of method names and subs and returns a blessed object. I think I made my goal of encapsulation because there is no easy way to access the instance variables and by using an inside-out design for the functions, it makes it difficult to mess with them. I haven't developed the inheritance part very much, but I think because the objects themselves are just keys I could probably modify it so objects blessed as other things could inherit from these objects.
The one thing that I am not sure about is the overhead part. It is my understanding that when closures are used that the different version of a closure share the same code but just use a different set of lexicals. So I think that the overhead for an individual object should just be the set of lexicals plus the references to the CODE objects. Is this a correct understanding of how closures work? My second question about overhead is the use of the "goto" in the methods (See the source for "methods" below). The documentation for goto says:
The "goto-&NAME" form is quite different from the other forms of "g +oto". In fact, it isn't a goto in the normal sense at all, and doesn +'t have the stigma associated with other gotos.
I interperted this as this form of goto does not have the same overhead as other forms of goto. I figure that if there is a little overhead that cost can be made up by the fact that inside the actual method there is no overhead for looking up an instance variable in a hash table or array. Is this a good assumption? Finally, I would like to know what my fellow monks think of this idea. It still needs some fleshing out, but I would like to know if there are some glaring flaws I have overlooked before I work on it anymore. Thanks!
package Class::FIOC; require Exporter; use strict; our @ISA = qw(Exporter); our @EXPORT = qw(methods); our %Classes; our $DEBUG=0; sub methods { my $class = caller(); my %method_map = %{+shift}; # any old ref will work, one day maybe the user can choose the ref my $self = bless [],$class; # see if we already have seen this class before if (my $method_names=$Classes{$class}) { while (my ($name,$ref)=each(%method_map)) { # validate reference refers to CODE if (ref($ref) ne "CODE") { die "values passed to 'methods' hash must be CODE references!\n"; } # validate that this is not a new method name # maybe new method names should be allowed? I don't know if (my $hash=$method_names->{$name}) { $hash->{$self}=$ref; }else { die "Cannot add new method '$name' to class $class!\n"; } } } else { my %method_names; while (my($name,$ref)=each(%method_map)) { my $public = $name=~s/^\+//; my %hash; # validate name is legal identifier if ($name!~/^[A-Za-z_]\w+/) { die "'$name' is not a valid identifier!\n"; } # validate reference refers to CODE if (ref($ref) ne "CODE") { die "values passed to 'methods' hash must be CODE references!\n"; } # create method my $eval = "sub $class\:\:$name { goto \$hash{\$_[0]}}\n"; eval $eval; # save the hash for later so we can store other instance methods + in it $method_names{$name}=\%hash; $hash{$self}=$ref; } $Classes{$class}=\%method_names; no strict 'refs'; *{"$class\:\:DESTROY"} = sub { my $self = shift; while (my($name,$method)=each(%method_names)) { delete $method->{$self}; if ($DEBUG) { print "Destroying '$name' method for $self\n"; } } } } return $self; } 1;

Comment on Functional Inside Out Closure Objects
Select or Download Code
Re: Functional Inside Out Closure Objects
by dave_the_m (Parson) on May 20, 2004 at 16:13 UTC
    In general closures have a lot higher overhead. Instead of storing a scalar variable as a hash entry, you are storing a reference to a CV structure, which points to a pad list which points to a padname list (containing the name of the lexical), and a pad containing a pointer to the lexicial plus pointers to other pad targets if there are any ops in the sub.

    goto &f has roughly the same overhead as a normal exit from a sub followed by a call to another sub; ie the following two are roughly equivalent performance-wise:

    sub f { ...; goto &g } sub g { ...} f(); sub f { ...; } sub g { ...} f(); g();
Re: Functional Inside Out Closure Objects
by stvn (Monsignor) on May 20, 2004 at 16:19 UTC
    I, like many people (See the number of Class::* modules on CPAN), have sought to make oop easier in perl.

    Personally, I never really thought of OOPerl as hard, ugly yes, primative maybe, clobbered on, for sure, but not really all that hard. :P

    It is my understanding that when closures are used that the different version of a closure share the same code but just use a different set of lexicals. So I think that the overhead for an individual object should just be the set of lexicals plus the references to the CODE objects. Is this a correct understanding of how closures work?

    This is pretty much my understand of closures as well. Although I cannot say for sure that it is correct :)

    My second question about overhead is the use of the "goto" in the methods

    You could likely get rid of the goto if you wanted to. Instead of evaling your code like this:

    # create method my $eval = "sub $class\:\:$name { goto \$hash{\$_[0]}}\n"; eval $eval;
    You could just do some symbol table mangling like this:
    *{"${class}::$name"} = $ref;
    The result should be the same, but no goto.

    -stvn
      Actually, this would not be the same. ${class}::$name would point to the CODE ref for the last object that was created. With the way I have it, the goto jumps to the CODE ref for the particular instance that the method is being called on.

        Nevermind me, I am still half asleep. You are right, I was reading the source wrong. However, in light of my new understanding of your module, I would tend to agree with dave_the_m that this is creating a rather large overhead for your module. Not to mention that you may find yourself having to deal with the nested closure memory leak issue.

        -stvn

      Personally, I never really thought of OOPerl as hard, ugly yes,

      Ugly? why?

      sth

        To start with, perl's object system was a bolt-on to the existsing module system, which is both ugly (because it kind of exposes the soft underbelly of objects) and cool at the same time (you can do some crazy stuff with modules/typeglobs, and you can now do them with objects too :)

        How it handles inheritance is another.

        package Foo; @ISA = qw(Bar);
        This takes the idea of inheritance and converts it into a "magic" laden assignment statement. This exposes too much of the details of how perl manages inheritance, which IMO is ugly. Maybe I am a purist, but I think that declaring inheritance should be part of the class declaration, or at the very least have its own keyword.

        Constructors in perl are nothing special, which at first glance is cool, but it does lead to some ugly code when calling base class constructors in subclasses. I personally like the C# syntax for these things:

        class Bar { string name; public Bar (string name) { this.name = name; } } class Foo : Bar { Hashtable attributes; public Foo (string name, Hashtable attributes) : base(name) { this.attributes = attributes; } }
        Also, the idea that an object is just a blessed reference IMO is another double edged sword. It makes encapsulation a big issue/problem. (Sure I know you can do Inside-Out objects and closures, etc etc etc, but all of them just add to the already touchy syntax.) Now I know the idea is that you shouldn't mess with the internals because you were not invited, and that it then becomes a social problem. But I would rather not have the social problem at all and have strong encapsulation.

        All this said, I love perl, so I have worked with/around all this stuff in my own way, but it doesn't change the underlying ugliness of it. And yes, some might call this elegance, and I will be the first to admit that there is a fair amount of elegance in how minimally invasive the adding of objects to perl was, but when you get deep into the thick of it, there is some real ugliness there no matter what.

        -stvn
Re: Functional Inside Out Closure Objects
by hardburn (Abbot) on May 20, 2004 at 16:56 UTC

    How would you implement subclassing?

      I haven't quit worked it out yet, but I think I would do something where if "methods" had a second arguement that was a blessed ref, it would not create a new blessed ref, but would just use the blessed ref it was given. Something like this:
      #!/usr/bin/perl package Foo; use Class::FIOC; use strict; sub new { my $class = shift; my $foo_thing = shift; my $subclass = shift || 0; methods { foo_thing=>sub { print "My foo_thing=$foo_thing\n"; } },$subclass; } #!/usr/bin/perl package Bar; use Foo; our @ISA qw(Foo); use Class::FIOC; use strict; sub new { my $class = shift; my $bar_thing = shift; my $self = methods { bar_thing=>sub { print "My bar_thing=$bar_thing\n"; } } return Foo->new("Hello",$self); }
Re: Functional Inside Out Closure Objects
by podian (Scribe) on May 20, 2004 at 17:05 UTC
    what about perl6? Are they tring to make OO easier?
      More powerful, yes. More transparent, yes. Cleaner, yes. More structurally similar to other OO languages, yes. More solidly based on OO principles, yes. Easier, that's hard to say. Read Apocalypse 12 and decide :)

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://354972]
Approved by kutsu
Front-paged by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (10)
As of 2014-09-18 18:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (120 votes), past polls