in reply to Perl and Objects, how do you resolve the two?

Perl object-orientation isn't OO. Well, it is. You can create objects (known as modules), tell them what they are (by blessing them), and create method inheritance (through @ISA). From a very nuts'n'bolts view, that's basically all you need for functional OO. Encapsulation and Inheritance. Data inheritance is often nice to have, too. But, that's easily done by being intelligent in your initialization function(s).

However, I have to vehemently disagree with the concept of using AUTOLOAD to do everything. Frankly, that's unmaintainable in the long run. (Not to mention being a little slower than previously-declared functions.) Here is a basic OO design I am currently using in a medium application and it works beautifully:

sub get_attribute_names_and_defaults { my $pkg = shift; $pkg = ref($pkg) if ref($pkg); my @result = @{"${pkg}::_ATTRIBUTES_"}; if (defined @{"${pkg}::ISA"}) { push @result, get_attribute_names_and_defaults($_) for @{"${pk +g}::ISA"}; } @result; } sub define_attributes { my $self = caller; my $pkg = ref($self) ? ref($self) : $self; my $pkg_string = "${pkg}::_ATTRIBUTES_"; if (defined @{$pkg_string}) { push @{$pkg_string}, @_; } else { @{$pkg_string} = @_; } } sub new { my $type = shift; my %args = @_; my $self = {}; bless $self, $type; my $attr_name; my $pkg = ref($self); my %defaults = Global::Generic_Object::get_attribute_names_and_def +aults($pkg ); for (keys %defaults) { $attr_name = Global::Generic_Object::_input_to_attribute(undef +, $_); unless (exists $self->{$attr_name}) { my $default_ref = ref($defaults{$_}); if ($default_ref eq 'ARRAY') { if (@{$defaults{$_}}) { $self->{$attr_name} = [ @{$defaults{$_}} ]; } else { $self->{$attr_name} = []; } } elsif ($default_ref eq 'HASH') { if (@{$defaults{$_}}) { $self->{$attr_name} = { %{$defaults{$_}} }; } else { $self->{$attr_name} = {}; } # } elsif ($default_ref eq 'SCALAR') { # $self->{$attr_name} = \$$defaults{$_}; # } elsif ($default_ref) { } else { $self->{$attr_name} = $defaults{$_}; } } } $self->_initialize(%object_hash, %args); return $self; } define_attributes(-REGISTERED_NAME => ''); sub _initialize { my $self = shift; $self->set(@_); return 1; } sub exists { my $self = shift; my @args = @_; my @return_list = (); foreach my $in (@args) { my ($obj, $data) = split (/\./, $in, 2); my $key = $self->_input_to_attribute($obj); if (exists $self->{$key}) { if ($data && ref $self->{$key} && ref($self->{$key}) =~ /: +:/) { push @return_list, $self->{$key}->exists("-$data"); } else { push @return_list, 1; } } else { push @return_list, 0; } } return @return_list == 1 ? $return_list[0] : @return_list; }

get(), set(), inc(), strcat(), and clr() all are defined in the same way. You have one function that handle attribute retrieval. That's it. Nothing more. Plus, this handles attribute inheritance as well.

Thoughts?

Replies are listed 'Best First'.
Re (tilly) 2: Perl and Objects, how do you resolve the two?
by tilly (Archbishop) on Apr 11, 2001 at 03:35 UTC
    How vehement do you want to get?

    Try it out on me. :-)

    First of all I would not advocate using AUTOLOAD for everything. But there are cases where it is a useful tool to know about. Use it wisely.

    For instance how would you write the equivalent of Re (tilly) 1: Nested Classes without AUTOLOAD? (At least and make it look like Perl's object syntax.)

    And yes. There is a performance hit. Which is why people play games like I did at Re (tilly) 1: Reverse Inheritance Irritance and auto-instantiate real methods. You pay the price of the AUTOLOAD once only per method that you use. Incidentally the pattern that I used in that post is a common one to see in a lot of OO languages. Generally done with some version of AUTOLOAD.

    So I hope that by practical example you see that there are things that AUTOLOAD is truly useful for. No, it should not replace everything under the sun. But it is a legitimate part of the toolkit.

    As for the example that you showed, my main complaint is that you are not using strict. Also I would want to export the functions from Global::Generic_Object using Exporter. Beyond that, I would need to know what your definition of medium sized is. If you mean a few thousand lines, I think you are waaay over-engineering it. If you mean 30,000, well I wonder about over-engineering. In a larger application? Plausible. But I would want to see the object model.

    In short, I would worry that is a design meant to support an object model that has grown out of control...

    As for whether Perl's OO really is, well it is simplistic, it is a little wordy, performance isn't great, but you can do OO and it works. You can play fun OO games and it works. You can lay something complex out and it works.

    But if you want OO nirvana, well Perl supports a lot of styles of programming as "second class citizens", OO among them. Perl is not the greatest language for OO. But it works...

      Medium-sized is 30,000 lines of actual code and 70,000 lines of script-generated templates. In addition, this model allows for extremely easy extensions of the application. (In fact, for other reasons, it has been rebuilt to allow for this.) The object model is clean-ish, if you're wondering.

      The way one uses that module is to inherit it through @ISA. Then, $self->get(-FOO) works quite nicely. $self->exists(-FOO) is included primarily for completeness than a real need for it. You don't Export the functions. (The only function I really want to Export is define_attributes, but I'm having problems getting define_attributes within the scope of grandchildren and further down. Any thoughts?)

      I still return to my main objection concerning AUTOLOAD being that it's much less maintainable. I inherited this project and (hopefully!) will be passing it on to someone else in the next 6-8 weeks. If I was passing on an application containing some 250 classes, every single one of them having their own AUTOLOAD, that's a maintenance nightmare! This way, the accessor methods are defined in one, and only one, place. Every object has the exact same API, meaning that learning the system is very easy. (Yes, learning $self->foo() is also easy. I'm whining. But, having the API to within the objects be consistent is still a "Good Thing"(tm).) I hand this off and my successor doesn't have to fight the implementation. That is also a "Good Thing"(tm).

      Yes, there are things that AUTOLOAD is good for. I would put forward that a production application isn't one of them.

        And here we see why phrases like "medium-sized" are not very informative. Your concept of "medium-sized" is off by an order of magnitude from what I think is customary in the Perl world.

        As for AUTOLOAD, I still disagree. I agree with you that the interfaces should be consistent and the implementations reasonably so. I agree that a standardized implementation helps with that. But AUTOLOADs at key places can still be of great assistance. For instance suppose you need an improvement in startup time. Why not use AutoLoader? If your usage pattern means that any given user only uses a small subset of your total application, the boost could be substantial.

        Is that choice going to be unmaintainable? I don't think so! It shouldn't be scattered everywhere. But think of it as being a templating tool. You have templates all over. Somewhere you have code that processes templates and autogenerates code. Would writing custom autogeneration of code everywhere be a horrible idea? Of course! But is having it used in a key way in a key place in your process invaluable? Of course!

        Ditto for AUTOLOAD. Think of it as a way to do lazy run-time templates. Useful. Easily abused, but also powerful when used in a controlled, appropriate manner.

        As for your specific question, what I didn't like was seeing the use of functions named by fully qualified package name. If you are doing that everywhere, that is a sign of a problem. If it is limited to things that internally should know each other, that is a different question altogether.

        As for exporting, all automatic exports in Perl are done with an import method. The usual semantics you see on CPAN are obtained by inheriting from Exporter. If you want to define your own custom import method, that is easy but I would think twice about having a radically different API. An example is at AbstractClass. (It has an import method that writes an import method into the calling class.) Another approach which might fit better into your system is to have a generic import method (something like Exporter has) but have the list of things you are willing to reexport add the things you imported. That allows for an "inheritance" of a functional interface through classes that will play well with multiple-inheritance though not (ironically enough) with AUTOLOAD.