skip to Main Content

Whenever you extend a class in JavaScript or Python, the derived class must use the super keyword in order to set attributes and/or invoke methods and constructor in the base class. For example:

class Rectangle {
    constructor(length, width) {
        this.name = "Rectangle";
        this.length = length;
        this.width = width;
    }

    shoutArea() {
        console.log(
            `I AM A ${this.name.toUpperCase()} AND MY AREA IS ${this.length * this.width}`
        );
    }
    
    rectHello() {
        return "Rectanglish: hello";
    }
}

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
        this.name = "Square"
    }
    
    squaHello() {
        const h = super.rectHello();
        return "Squarish:" + h.split(':')[1];
    }
}

const rect = new Rectangle(6, 4);
rect.shoutArea(); //=> I AM A RECTANGLE AND MY AREA IS 24

const squa = new Square(5);
squa.shoutArea(); //=> I AM A SQUARE AND MY AREA IS 25

console.log(squa.squaHello()); //=> Squarish: hello

2

Answers


  1. What’s the Raku equivalent of the super keyword as used in JavaScript and Python?

    One of Raku’s re-dispatching functions

    A simple example of redispatch

    First, some code that doesn’t involve explicit use of redispatch:

    class Rectangle {
      has ($.length, $.width)
    }
    
    Rectangle.new: length => 6, width => 4;
    

    The Rectangle declaration omits any explicit construction code. This means the Rectangle.new call uses the default new method provided by Raku’s Mu class. The default new initializes any class attributes whose names match any named arguments.


    If you instead want a custom new constructor that accepts positional arguments, then you typically write one which lists them in its signature and then calls the default .new with the arguments converted to named arguments:

    class Rectangle {
      has ($.length, $.width);
      method new ($length, $width) { callwith length => $length, width => $width }
    }
    
    Rectangle.new: 6, 4;
    

    callwith:

    • Redispatches a call to the next matching candidate based on the original call.²

    • Calls the next candidate with a fresh set of arguments.

    In this simple case the original call was Rectangle.new: 6, 4, and the next candidate is the default .new from Mu.

    A fancier Rectangle class

    Rather than mimic your code with minimal changes I’ll write more idiomatic Raku code and comment on it.

    class Rectangle {
      has ($!length, $!width) is required is built;
      method new ($length, $width) { callwith :$length, :$width }
      method shoutArea { put uc "I am a {self.^name} and my area is {$!length * $!width}" }
      method rectHello { 'Rectanglish: hello' }
    }
    
    constant rect = Rectangle.new: 6, 4;
    rect.shoutArea; #=> I AM A RECTANGLE AND MY AREA IS 24
    

    Commentary:

    • It’s a good habit to default to writing code that limits problems that can arise as code evolves. For this reason I’ve used $!length for the length attribute rather than $.length

    • I’ve added an is required annotation to the attributes. This means a failure to initialize attributes will raise an exception.

    • I’ve added an is built annotation to the attributes. This means that even attributes without a public accessor can/will still be automatically initialized if there are matching named arguments in the construction call.

    • :$length is short for length => $length.

    • self.^name avoids unnecessary overhead. It’s not important so feel free to ignore my footnote explaining it.⁴

    Adding the Square class

    I’ll make the .new for Square redispatch to the one for Rectangle:

    class Square is Rectangle {
      method new ($side-length) { callwith $side-length, $side-length }
      method squaHello { "Squarish: {self.rectHello.split(':')[1].trim}" }
    }
    
    constant squa = Square.new: 5;
    squa.shoutArea; #=> I AM A SQUARE AND MY AREA IS 25
    put squa.squaHello; #=> Squarish: hello
    

    Commentary:

    • I picked the name $side-length for the Square‘s .new parameter, but the name doesn’t matter because it’s a positional parameter/argument.

    • self.rectHello suffices because the method being called has a different name than the originally called method (squaHello). If you renamed the two methods in Rectangle and Square to have the same name Hello then a callsame redispatch would be appropriate. (Unlike callwith, which must be followed by the arguments with which the call is to be redispatched, callsame takes no arguments and just redispatches with the same arguments that were provided in the original call.)

    Footnotes

    ¹ Redispatching is a generalization of features like super. Redispatch functions are used for a range of purposes, including ones that have nothing to do with object orientation.

    ² In Raku a function or method call may result in the compiler generating a list of possibly matching candidates taking into account factors such as invocants for method calls, and multiple dispatch and function wrappers for both functions and methods. Having constructed a candidate list it then dispatches to the leading candidate (or the next one in the case of redispatch to the next candidate).

    ³ If you really want a getter/setter to be automatically generated for a given attribute, then declare it with a ., eg $.length instead of $!length, and Raku will generate both a $!length attribute and a .length getter. (And a setter too if you add an is rw to the $.length declaration.) I did this in the first code example to keep things a bit simpler.

    ⁴ The ^ in a method call like foo.^bar means a bar method call is redirected "upwards" (hence the ^) to the Higher Order Workings object that knows how a foo functions as a particular kind of type. In this case a Rectangle is a class and the HOW object is an instance of Perl6::Metamodel::ClassHOW, which knows how classes work, including that each class has a distinct name and has a .name method that retrieves that name. And the name of the Rectangle class is of course ‘Rectangle’, so self.^name saves having to create something else with the class’s name.

    Login or Signup to reply.
  2. Here is my cut at your example in raku:

    class Rectangle {
        has $.name = "Rectangle";
        has $.length;
        has $.width;
    
        method area { $!length * $!width }
    
        method shoutArea {
            say "I AM A {$.name.uc} AND MY AREA IS {$.area}";
        }   
    
        method hello-prefix {
            "Rectanglish:"
        }   
    
        method hello {
            $.hello-prefix ~ ' hello'
        }   
    }
    
    class Square is Rectangle {
        has $.name = "Square";
        has $.side;
        
        method area { $!side ** 2 }
        
        method hello-prefix {
            "Squarish:"
        }
    }
    
    my $rect = Rectangle.new(length => 6, width => 4);
    $rect.shoutArea(); # => I AM A RECTANGLE AND MY AREA IS 24
    
    my $squa = Square.new(side => 5);
    $squa.shoutArea(); # => I AM A SQUARE AND MY AREA IS 25
    
    say $squa.hello; # => Squarish: hello
    

    To answer your question on ‘super’, as mentioned by @raiph, this is not needed in raku due to the mutli dispatch mechanism.

    It seems that the ‘super’ keyword needs methods to "know" where they sit (in the child or the parent). In raku, this can be handled more generally with nextsame, nextwith, callsame, callwith.

    But, as my rewrite shows, often you can avoid this by rephrasing the methods to maintain encapsulation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search