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
One of Raku’s re-dispatching functions.¹
A simple example of redispatch
First, some code that doesn’t involve explicit use of redispatch:
The
Rectangle
declaration omits any explicit construction code. This means theRectangle.new
call uses the defaultnew
method provided by Raku’sMu
class. The defaultnew
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: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
fromMu
.A fancier
Rectangle
classRather than mimic your code with minimal changes I’ll write more idiomatic Raku code and comment on it.
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 forlength => $length
.self.^name
avoids unnecessary overhead. It’s not important so feel free to ignore my footnote explaining it.⁴Adding the
Square
classI’ll make the
.new
forSquare
redispatch to the one forRectangle
:Commentary:
I picked the name
$side-length
for theSquare
‘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 inRectangle
andSquare
to have the same nameHello
then acallsame
redispatch would be appropriate. (Unlikecallwith
, 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 anis rw
to the$.length
declaration.) I did this in the first code example to keep things a bit simpler.⁴ The
^
in a method call likefoo.^bar
means abar
method call is redirected "upwards" (hence the^
) to the Higher Order Workings object that knows how afoo
functions as a particular kind of type. In this case aRectangle
is a class and the HOW object is an instance ofPerl6::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 theRectangle
class is of course ‘Rectangle’, soself.^name
saves having to create something else with the class’s name.Here is my cut at your example in raku:
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.