http://www.perlmonks.org?node_id=176963

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

Hi, I'm having trouble using SUPER to call an ancestor's constructor. Say i've got an abstract class, Word, and a subclass, Noun, which derives from it, and calls it's parent's constructor, but then takes a few extra parameters. The only way I can get it to work is to create and bless my $self and then call SUPER:
#!/usr/bin/perl -w use strict; package Word; sub new { my $classname = shift; my $self = {}; $self->{stem} = shift; $self->{english} = shift; bless $self, $classname; return $self; } package Noun; our @ISA = "Word"; sub new { my $classname = shift; my $self = {}; bless $self, $classname; $self = $self->SUPER::new(shift, shift); $self->{gender} = shift; $self->{nom_sg} = shift; $self->{gen_sg} = shift; return $self; } package main; my $w = new Noun("ai(=m-", "blood", "n", "a", "atos"); print $w->{english};
This seems very clunky & makes me think it's hardly worth setting up the inheritance. But I know that in Java you can do it in one line, so I bet you can in Perl too? Can anyone suggest an optimised version of the above?

Replies are listed 'Best First'.
Re: inheritance: constructors
by djantzen (Priest) on Jun 24, 2002 at 23:22 UTC

    Short answer: $classname->SUPER::new(@_)

    Long answer:

    Mimicking Java's inheritance behavior exactly is difficult in Perl without adding a bunch of overhead that the Java compilers normally handle; namely, inserting calls to super() in cases where your subclass constructor does not explicitly call the superclass constructor, and also generating zero-argument constructors to be used by subclasses of your subclass. In Java, every time you create an object you instantiate the class's ancestors all the way up to java.lang.Object, and in order to facilitate this there must be generic, default constructors available. Update/Correction: The compiler will insert a constructor for you in the total absence of a constructor. If you've already got one that takes arguments, it's up to you to provide a zero-arg version unless you wish to force subclasses to specify arguments at all times.

    You can get this behavior in Perl with code like:

    sub new { my ($class, @args) = @_; my $this; unless (defined @args) { $this = $class->SUPER::new(); # call zero-arg ctor return bless($this, $class); # rebless to our class } else { # do argument checking to figure out what ought to go # to the superclass ctor and what needs to be initialized here. $this = $class->SUPER::new(@some_args); # then initialize the parts of the object that are unique to this +class return bless($this, $class); # rebless to our class }

    An easier solution is to use named lists to pass arguments and to stuff those into a hash.

    sub new { my ($class, %args) = @_; my $this = $class->SUPER::new(%args); # Do additional processing on the args, e.g ... $$this{foo} = $args{foo}; return bless($this, $class); # rebless to our class }

    My current favorite way to handle inheritance is to separate creation from initialization. Write a constructor in your top-level class to be inherited, like:

    package SomeSuperclass; sub new { my $class = shift(); my $this = {}; bless($this, $class); $this->_init(@_); return $this; }

    Any subclass can then define a "protected" method _init to 1) call superclass initialization methods, and 2) finish initialization for this class.

    package SomeSubclass; use base SomeSuperclass; sub _init { my ($this, $foo, $bar, $baz) = @_; $this->SUPER::_init($bar, $baz); $$this{foo} = $foo; }

    As always, mad props to TheDamian's book Object Oriented Perl

      Please, I beg you, do not tell people they should use an else/unless construct. Ever. I wish this wasn't even possible. People need to think in the proper order.
        See? I can't even think "unless/else" properly. Ugh. Backwards thinking is why so many of us Perlmongers get made fun of by the other programmers. And why our successors curse our names when they have to maintain our code. Stop the madness.
Inheritance and NEXT
by pjf (Curate) on Jun 25, 2002 at 00:20 UTC
    Splitting construction from initialisation is a very common, clean, and recommended way of proceeding. It's already been covered, so I won't repeat it here. It's a good idea to do this even if you're not doing inheritance at the point -- things often change, and one of the advantages of OOP is that objects are supposed to be inheritable.

    For a clean and easy way to call parent initialisers "when appropriate", you might consider the use of NEXT. NEXT will call your parent's constructor if it exists and if it's appropriate. It will handle multiple inheritance without difficulty. It's also going to be a standard module in Perl 5.8.0.

    use NEXT; # ... sub _init { my $self = shift; $self->NEXT::UNSEEN::_init(@_); # My init goes here... }
    You can find a whole chapter on NEXT and why it's useful in the Object Oriented Perl training notes from Perl Training Australia. TheDamian has also written an article on use.perl which can be found here.

    Cheers,
    Paul Fenwick
    Perl Training Australia

Re: inheritance: constructors
by dws (Chancellor) on Jun 24, 2002 at 22:26 UTC
    Inheritance in Perl is indeed clunkier to set up than in other languages. Is it worth it? That depends on what you're doing.

    On to the particulars of your example:

    In Noun::new(), you need to either defer blessing or re-bless after you've invoked SUPER::new(). Unless you do, Noun::new will return instances of Word. Not correct: see the post below.

    A cleaner way is to separate "initialization" from creation.

    package Word; sub new { my $pkg = shift; my $self = bless {}, $pkg; $self->initialize(@_); $self; } sub initialize { my $self = shift; $self->{stem} = shift; $self->{english} = shift; } package Noun; use base qw(Word); sub new { my $pkg = shift; bless {}, $pkg; $self->initialize(@_); $self; } sub initialize { my $self = shift; $self->SUPER::initialize(shift, shift); $self->{gender} = shift; $self->{nom_sg} = shift; $self->{gen_sg} = shift; }
    This cleans things up, at the expense, arguably, of more "clunk". Then we have to add a bit more clunk to deal with print $w->{english}; which commits the great OO sin of assuming private details of an object. The official way out of this is to add an "accessor" method to your base class.
    sub english { my $self = shift; return $self->{english}; }
      In Noun::new(), you need to either defer blessing or re-bless after you've invoked SUPER::new(). Unless you do, Noun::new will return instances of Word.

      This is not correct. The original author correctly designed the parent-class new constructor to bless based on the class it was called from-- in the case of the example above, Word::new will in fact bless the new object into the Noun class, because that is the value of $classname at that point.

      --rjray

Re: inheritance: constructors
by frankus (Priest) on Jun 25, 2002 at 11:43 UTC
    IMO it's more "future facing" to use a loop on @ISA and the can method.
    It enables the Object to handle multiple inheritance.
    $self->$_($params) for ( map{ $_->can("_init")||()} @ISA );
    This'll execute a "private" method to initialise the correct variables for the object.
    As a rule, I pass all Parameters for the function inside a hashref called $params, it makes for easier coding and ledgibility.
    I generally have a class property called @manifest, listing the object properties and use a line like:
    $self->{$_ns . $_} = $params->{$_} || '' for @manifest;
    

    Where $_ns is the namespace as all the gubbins is stored in one hash, it's best not to overwrite stuff.
    I use a class property like this:
    my $_ns = __PACKAGE__; 
    
    For I find there are times when __PACKAGE__ doesn't want to play.
    I also apply accessor and mutator functions for all the properties:
    sub get_spong() { $_[0]->{ $_ns . 'spong' } }
    
    And because all of this is labour-intensive and boring,
    I set up a template and also some generic parent classes to ingherit most of the common stuff from.

    As a rule of thumb Perl OO is tricky, because it's Perlish OO.
    You can rewrite OO from first principals.
    Perl OO can be clunky: it mirrors the developers understanding of OO principals since Perl is so liberal.

    --

    Brother Frankus.

    ¤

Re: inheritance: constructors
by tobyink (Canon) on Dec 01, 2012 at 12:48 UTC

    This is why you ought to use Moose, or a Moose-work-alike such as Moo for pretty much any OO. You never need to write another constructor again!

    use v5.14; use strict; use warnings; package Word { use Moo; has stem => (is => 'ro'); has english => (is => 'ro'); } package Noun { use Moo; extends 'Word'; has gender => (is => 'ro'); has nom_sg => (is => 'ro'); has gen_sg => (is => 'ro'); } my $w = Noun->new( stem => 'ai(=m-', english => 'blood', gender => 'n', nom_sg => 'a', gen_sg => 'atos', ); say $w->english;
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'