glasswalk3r has asked for the wisdom of the Perl Monks concerning the following question:

Greetings,

When I started learning OOP for Perl, I got some advices about what to do with abstract methods defined in superclasses. The suggestion usually goes like this:

sub to_string { die "This method must be overridden by a subclass of __PACKAGE__"; }

While this works fine, it feels unpleasant to repeat such code everytime I create a superclass with abstract methods. The module Attribute::Abstract helped with that (thanks Dan!).

Although laziness issue is fixed, now I think it would be better if the code could advice the programmer that he/she forgot to override some method that, for any reason, was not invoked during program execution. Later, of course, this error would popup. In a perfect world, unit testing would catch up this bug, but sometimes people just don't test the program enough.

I decided then to start extending the Attribute::Abstract to be able to, optionally, generate warning messages regarding methods that were not overrided by a subclass. The code will write warning messages thru DESTROY method implemented in the superclass, so this allows the use of code generators like Class::Accessor.

That said, I would like to receive some feedback from you monks about how do you address this kind of issue and regarding the code that I wrote as a solution (it follows next). I'm not sure if this code will work with multiple inheritance either.

Modified version of Attribute::Abstract:

package Attribute::Abstract; use warnings; use strict; use Attribute::Handlers; our $VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)/g; sub UNIVERSAL::Abstract : ATTR(CODE) { my ( $pkg, $symbol ) = @_; no strict 'refs'; my $sub = $pkg . '::' . *{$symbol}{NAME}; *{$sub} = sub { my ( $file, $line ) = (caller)[ 1, 2 ]; die "call to abstract method $sub at $file line $line.\n"; }; # creates a symbol table in the caller package to hold an array # with the abstract methods that it has my $abstract_methods = $pkg . '::abstract_methods'; # array ref my $abstract_ref = *{$abstract_methods}{ARRAY}; #checking if the array holding the abstract methods already exists if ( defined($abstract_ref) and ( @{$abstract_ref} ) ) { push( @{$abstract_ref}, *{$symbol}{NAME} ); } else { @{$abstract_methods} = ( *{$symbol}{NAME} ); } my $destroy_sub = $pkg . '::DESTROY'; # no need to redefine everytime the DESTROY sub unless ( defined( *{$destroy_sub}{CODE} ) ) { *{$destroy_sub} = sub { use strict; my $self = shift; my $class_name = ref($self); no strict 'refs'; # alias to check if the method exists in the package (subc +lass) # hash reference my $subclass_ref = *{ $class_name . '::' }{HASH}; # fetch the array with the abstract methods from the superclass # assuming that __PACKAGE__ holds de value of the superclass that uses + Attribute::Abstract method # array reference my $abstract_ref = *{ $pkg . '::abstract_methods' }{ARRAY} +; foreach my $method ( @{$abstract_ref} ) { print STDERR "Abstract method '$method' of $pkg was not overrided by $class_name\n" unless ( exists( $subclass_ref->{$method} ) ); } } } } #"Rosebud"; # for MARCEL's sake, not 1 -- dankogai 1;

Animal superclass:

package Animal; use Attribute::Abstract; use strict; use warnings; sub new { my $class = shift; # hash reference my $self = shift; bless $self, $class; return $self; } sub get_sound : Abstract; sub get_color : Abstract; 1;

Animal::Dog subclass:

package Animal::Dog; use base qw(Animal Class::Accessor); __PACKAGE__->follow_best_practice(); __PACKAGE__->mk_accessors(sound); __PACKAGE__->mk_ro_accessors(name); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{sound} = '"bark bark!"'; return $self; } 1;

Animal::Duck subclass:

package Animal::Duck; use base qw(Animal Class::Accessor); __PACKAGE__->follow_best_practice(); __PACKAGE__->mk_ro_accessors(qw(name color)); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{sound} = '"quack quack!"'; $self->{color} = 'I am white damn it!'; return $self; } 1;

Finally the proof of concept:

use strict; use warnings; use Animal::Dog; use Animal::Duck; my $dog = Animal::Dog->new( { name => 'Atila' } ); my $duck = Animal::Duck->new( { name => 'Donald' } ); print $dog->get_name(), ' says ', $dog->get_sound(), "\n"; print $duck->get_name(), ' says ...', "\n";

Executing it...

Atila says "bark bark!" Donald says ... Abstract method 'get_sound' of Animal was not overrided by Animal::Duc +k Abstract method 'get_color' of Animal was not overrided by Animal::Dog

Hints? Implementation suggestions? Thank you all.

Regards,

Alceu Rodrigues de Freitas Junior
---------------------------------
"You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill

Replies are listed 'Best First'.
Re: Dealing with abstract methods in Perl 5.8
by samtregar (Abbot) on Apr 24, 2007 at 17:43 UTC
    Interesting idea. It seems to rely on the sub-classes not using DESTROY themselves, correct? I wonder if DESTROY is the right hook... Could you use a CHECK block to run the checks on sub-classes, perhaps? I imagine you could write code to iterate through all defined packages looking for packages which have missing abstract overrides. If not CHECK, perhaps END.

    -sam

      Yes, that is true: the solution relies on having the subclasses at least to execute the parent class DESTROY method though SUPER. Using DESTROY was my best bet because of classes like Class::Accessor that creates code during execution phase. Honestly, I don't know if CHECK and/or END blocks manipulation will not broke code generators more than using DESTROY method...

      But iterating all over packages looking for methods that were not override looks like a bit overkill, since the parent class already knows which abstract methods it has. Anyway, how would you do it? I mean, how to mark an method as abstract? Or do you mean to really start checking every package for a "@abstract_methods" (or anything else) available as an indication this class must be checked?

      Alceu Rodrigues de Freitas Junior
      ---------------------------------
      "You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill
        I think an END block might be a minor improvement. You could implement the search any number of ways - probably the easiest would be to create a list of packages that need checking as you go. Then when END comes around you know where to look - similar to @abstract_methods, but a single list of packages.

        Another advantage of using END is that it executes once per process, not once per object. A system that uses lots of little objects could get much slower with your code... Also, it will catch missing methods for objects which are not created in the process.

        -sam

Re: Dealing with abstract methods in Perl 5.8
by bennymack (Pilgrim) on Apr 25, 2007 at 00:18 UTC

    I too am interested in making interface style classes more automated. I'm not a big fan of stuffing things in UNIVERSAL however.

    If it were me implementing this, I'd make the Attribute::Abstract module the super class of the interface modules like so:

    use Attribute::Abstract; BEGIN { our @ISA = qw[Attribute::Abstract]; }

    That way you don't need to stuff anything into UNIVERSAL. Then, in Attribute::Abstract, I'd register all modules that use() it (via inheritance of an import()) and check their @ISA for Attribute::Abstract and cause death at compile time for non-overidden methods.

    Of course this is all 100% conjecture. I'm not sure if it's possible, etc. But it's the direction I would go in if posed this particular problem.

      I'm not sure if I agree with you about putting the code into UNIVERSAL. Anyway, looks quite logical since is the kind of function that any superclass should use. Why do you think is not a good idea?

      Regarding the exception during compilation time, this would kill any module that generates code during execution time. Since we're talking about Perl after all, this is really a bad thing to do and I don't think is worth to do it to just receive a die because the programmer forgot to override a method.

      Alceu Rodrigues de Freitas Junior
      ---------------------------------
      "You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill
Re: Dealing with abstract methods in Perl 5.8
by rhesa (Vicar) on Apr 26, 2007 at 22:29 UTC

      Thanks for the suggestion. I'll test these modules, a quickly look in the code shows that they use a different approach to declare abstract methods (using import) but the idea is basically the same.

      In the other hand Class::Virtually::Abstract will not work with code generator modules and it uses eval, something I prefer not to use.

      Updated

      Here it goes the test description. First I modified the original code to use Class::Virtual in the Animal class:

      Then I changed the test.pl script:

      Finally, the results:

      C:\>test.pl Atila says "bark bark!" Animal::Duck forgot to implement get_sound at C:\test.pl line 12. Animal::Duck forgot to implement get_sound() at C:\test.pl line 14

      While using the method virtual_methods looks more simple to use than having to type several times sub something :Abstract the module forces you to use more code (using the missing_methods method) just to check if you didn't forgot something.

      I think I'll change the Attribute::Abstract to use something like virtual_methods: while using Attribute::Handlers looks fancy, is not really necessary.

      Alceu Rodrigues de Freitas Junior
      ---------------------------------
      "You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill
Re: Dealing with abstract methods in Perl 5.8
by Anonymous Monk on Apr 25, 2007 at 13:59 UTC

    All this tells me one thing: Perl does not support abstract method natively, and that's why you are introducing this junky. The title is misleading, by mentioning 5.8, you can easily mislead newbies to believe you are dealing with something native to Perl, even potentially a particular version.

    Perl is not OO, so why waste all this energy to fake it. If OO is the preference, go with some other language, for example Ruby.

      All this tells me one thing: Perl does not support abstract method natively, and that's why you are introducing this junky.

      Well, it does not, really. So what? I'm not addicted to OOP and I don't believe it is the solution for everything. Said so, I really don't care if Perl is not 100% OO.

      But I still like to code with Perl and I think that the OO implementation is good enough for my needs (well, almost). Is it wrong to try to make things better? I would be pleased to see some better proposal from you (whoever you are anyway, you didn't even bother to identify yourself) instead of some useless procrastination.

      Perl 5.8.8 does not have any feature that helps in this case? Thanks for telling me that, because that is the version I'm using.

      Alceu Rodrigues de Freitas Junior
      ---------------------------------
      "You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill
      Ruby doesn't have abstract methods either. You'd pretty much have to implement them in a similar fashion as here. (Same thing with Smalltalk for that matter). Abstract methods, or lack thereof do not OO make.