Search code examples
classperlinheritance

Passing parameters to parent constructor using Perl's new class syntax


This question concerns Perl's class syntax (new as of Perl 5.38.0). I have two classes, Rectangle and Square, with Square inheriting from Rectangle. They have different constructor arguments; Rectangle expects length & height while Square expects just side. My hope was that Square would be able to inject the length and height arguments into the constructor for Square's parent Rectangle object, based on side's value. Something akin to this, though the BUILD syntax from Object::Pad doesn't work:

class Rectangle
{
    field $length :param;
    field $height :param;

    method get_area { return $length * $height; }
}

class Square :isa(Rectangle)
{
    field $side :param;

    BUILD {
        my(%args) = @_;
        my($side) = $args{'side'};

        push(@_, length => $side, height => $side);
    }
}

Here's my test script:

my($rect) = Rectangle->new(length => 5, height => 6);
say $rect->get_area();

my($sq) = Square->new(side => 5);
say $sq->get_area();

My expected output would be:

30
25

Is there a way to do this in 5.38.x? I'm thinking that the class implementation is simply not evolved enough yet to do this.


Solution

  • The only way to hook into instantiation provided by the new class feature are ADJUST blocks. ADJUST blocks have no access to the arguments of the constructor call. They can see $self and instance variables of their own package. Also they are invoked 'top down' (parent class first). So to make your example work you have to:

    1. make the params to the parent class optional
    2. add accessor methods to the parent class
    3. use an ADJUST block in the child to set the parents fields

    This results in

    use v5.38;
    use feature 'class';
    class Rectangle
    {
        field $length :param = 0;
        field $height :param = 0;
        method set_length($arg) { $length = $arg }
        method set_height($arg) { $height = $arg }
        method get_area { return $length * $height; }
        ADJUST {
            say "in Rect ADJUST";
        }
    }
    
    class Square :isa(Rectangle)
    {
        field $side :param;
        ADJUST {
            say "in Square ADJUST";
            $self->set_height($side);
            $self->set_length($side);
        }
    }
    
    
    
    my($rect) = Rectangle->new(length => 5, height => 6);
    say $rect->get_area();
    my($sq) = Square->new(side => 5);
    say $sq->get_area();
    

    prints

    in Rect ADJUST
    30
    in Rect ADJUST
    in Square ADJUST
    25
    

    This feels like an ugly workaround to get past the limitations of the class feature as it is. I would not use it.

    Another way to solve this would be to make the length and height attributes overridable by using accessor methods in Rectangle::get_area :

    use v5.38;
    use feature 'class';
    class Rectangle
    {
        field $length :param = 0 ;
        field $height :param = 0 ;
        method get_length { $length }
        method get_height { $height }
        method get_area { return $self->get_length * $self->get_height; }
    }
    
    class Square :isa(Rectangle)
    {
        field $side :param;
        method get_length { $side }
        method get_height { $side }
    }
    
    
    my($rect) = Rectangle->new(length => 5, height => 6);
    say $rect->get_area();
    my($sq) = Square->new(side => 5);
    say $sq->get_area();