This HTML version of Think Perl 6 is provided for convenience, but it is not the best format of the book. You might prefer to read the PDF version. Chapter 12 Classes and ObjectsAt this point you know how to use functions to organize code and built-in types to organize data. The next step is to learn “object-oriented programming,” which uses programmer-defined types to organize both code and data. When software applications start to grow large, the number of details to be handled becomes overwhelming. The only way to manage this complexity is to use abstraction and encapsulation. Object orientation is a very popular and efficient way to implement abstraction and encapsulation. Perl 6 is an object-oriented programming language, which means that it provides features that support object-oriented programming, which has these defining characteristics:
Object-oriented programming in Perl 6 is a big topic that may be worth a book by itself (and there will probably be a book or two on the subject at some point). This chapter will hopefully do more than just skim the surface and enable you to create and use objects, but will not cover some of the details and more advanced features. 12.1 Objects, Methods and Object-Oriented ProgrammingLet us start with a high-level nontechnical overview of object-oriented programming in general and a brief introduction to the jargon associated with it. In computer science, an object may loosely describe a memory location or an entity having a value, and often be referred to by an identifier. This can be a variable, a data structure, an array, or possibly even a function. This general meaning is not the sense that we will use in this chapter. In object-oriented programming (OOP), the word object has a much more specific meaning: an object is an entity which often has:
In brief, an object is a set of attributes and methods packed together. Objects are usually defined in a kind of code package called a class. A class defines the methods and the nature of the attributes associated with an object. In Perl 6, a class makes it possible to define new types similar to the built-in types that we have seen before. Very soon, we will start to define some classes and to use them to create objects. You already know informally what a method is, as we have used built-in methods throughout the book. It is a sort of function with a special postfix syntax using the dot notation on the invocant. For example, you may invoke the say method on a simple string: "foo".say; # -> foo Note that “foo” isn’t an object, but a simple string, but
you can invoke the You probably also remember that methods can be chained in a process where the value returned by a method becomes the invocant for the next method: "foo".uc.say; # -> FOO my @alphabet = <charlie foxtrot alpha golf echo bravo delta>; @alphabet.sort.uc.say; # prints: ALPHA BRAVO CHARLIE DELTA ECHO FOXTROT GOLF In OOP, methods applicable to objects are usually defined within classes, often the class that also defined the object or some other class closely related to it. In Perl 6, methods can also be defined in a role, which is another type of code package somewhat resembling to a class, as we will see later. The basic idea of object-oriented programming is that an object is a kind of black box that hides its internals (data and code) from the user; the user can consult or change the state of an object through the methods. Hiding the internals of objects is called encapsulation. This often enables a higher-level view and a better data abstraction than what we have seen so far; this in turns helps to make programs less buggy (especially large programs). In addition, OOP usually also offers the following concepts:
We will now study how all these concepts are implemented in Perl. 12.2 Programmer-Defined TypesWe have used many of Perl’s built-in types; now we are going to define a new type. As an example, we will create a type called Point2D that represents a point in two-dimensional space. In mathematical notation, points are often written in parentheses with a comma separating the coordinates. For example, in Cartesian or rectangular coordinates, (0,0) represents the origin, and (x,y) represents the point x units to the right and y units up from the origin. x is called the abscissa of the point, and y the ordinate. There are several ways we might represent points in Perl:
Creating a new type is a bit more complicated than the other options, but it has advantages that will be apparent soon. A programmer-defined type is usually created by a class (or a role, but we will come back to that later). A barebones class definition for a point type looks like this: class Point2D { has $.abscissa; # "x" value has $.ordinate; # "y" value } The header indicates that the new class is called Point2D. The body is defining two attributes, i.e., named properties associated with the class, here the abscissa and ordinate (or x and y coordinates) of the point. Defining a class named Point2D creates a type object. The type object is like a factory for creating objects. To create a point, you call the new method on the Point2D class: my $point = Point2D.new( abscissa => 3, ordinate => 4 ); say $point.WHAT; # -> (Point2D) say $point.isa(Point2D) # -> True say $point.abscissa; # -> 3 You can of course create as many points as you wish. The new method is called a constructor and has not been defined in this example; this is not needed because Perl 6 supplies a default new constructor method for every class (we’ll see later how). The method invocation syntax, with the dot notation, is the same as what we have used throughout the book to invoke built-in methods. You are not forced to use this constructor; you can also create your own (and you may name it new or something else), but we will stay with the built-in new method for the time being. Creating a new object with a class is called instantiation, and the object is an instance of the class. Every object is an instance of some class, so the terms “object” and “instance” are interchangeable. But we will often use “instance” to indicate that we are talking about an object belonging to a programmer-defined type. 12.3 AttributesThe attributes that we have defined are properties associated with the Point2D class, but they are specific to the instance of the class that has been created. They are instance attributes. If we create another Point2D object, it will also have these attributes, but the values of these attributes are likely to be different. Figure ?? shows the result of these assignments. A state diagram that shows an object and its attributes is called an object diagram.
The variable $point refers to a Point2D object, which contains two attributes. Each attribute of the Point2D class should refer to a number, but this is not obvious in the current definition of the class. As it stands right now, we could create a Point2D object with a string for the abscissa, which would not make much sense. We can improve the class definition by specifying a numeric type for the attributes: class Point2D { has Numeric $.abscissa; # "x" value has Numeric $.ordinate; # "y" value }
The instance attributes are private to the class, which means
that they normally cannot be accessed from outside the class:
you would usually need to invoke a method of the class
(i.e., a kind of subroutine defined within the class), to get
their value. However, when an attribute is defined with a dot
as in has $.abscissa; Perl automatically creates an implicit accessor method, i.e., a method having the same name as the attribute that returns the value of this attribute. Thus, when we wrote: say $point.abscissa; # -> 3 we were not accessing directly the abscissa attribute of
the You can use such an accessor with dot notation as part of any expression. For example: my $dist-to-center = sqrt($point.abscissa ** 2 + $point.ordinate ** 2); There is another way to declare an attribute in a class, with an exclamation mark twigil instead of a dot: has $!abscissa; In that case, Perl does not create an implicit accessor method and the attribute can only be accessed from methods within the class. Such an attribute is now fully private. However, if you declare attributes this way, you will not be able to populate them at object creation with the default new constructor and will need to create your own constructor (or indirectly modify new). So don’t try that for the time being, as you would not be able to do much with your objects at this point. We’ll get back to that later. By default, object attributes are not mutable; they are read-only. This means you cannot modify them once the object has been created. This is fine for some attributes: if an object represents a person, that person’s name and birth date are unlikely to change. Some other attributes may need to be updated, sometimes very frequently. In such cases, attributes can be declared to be mutable with the is rw trait: class Point2D { has Numeric $.abscissa is rw; # "x" value has Numeric $.ordinate is rw; # "y" value } It is now possible to modify these attributes. For example, we can change the newly created point’s abscissa: # First creating a Point2D object: my $point = Point2D.new(abscissa => 3, ordinate => 4); say $point; # -> Point2D.new(abscissa => 3, ordinate => 4) # Now moving the $point object two units to the right: $point.abscissa = 5; say $point; # -> Point2D.new(abscissa => 5, ordinate => 4) Almost all of the information presented so far about attributes has been related to instance attributes, i.e., to properties related to individual objects. You can also have attributes pertaining to the whole class, which are named class attributes. They are less common than instance attributes and are declared with the my declarator (instead of has). A typical example of a class attribute would be a counter at the class level to keep track of the number of objects that have been instantiated. 12.4 Creating MethodsThe simple Point2D class and its instance class Point2D { has Numeric $.abscissa; has Numeric $.ordinate; method coordinates { # accessor to both x and y return (self.abscissa, self.ordinate) } method distance2center { (self.abscissa ** 2 + self.ordinate ** 2) ** 0.5 } method polar-coordinates { my $radius = self.distance2center; my $theta = atan2 self.ordinate, self.abscissa; return $radius, $theta; } } We declare three methods in the class:
A method definition is not very different from a subroutine definition, except that it uses the method keyword instead of the sub keyword. This is not a surprise since a method is essentially a subroutine that is defined within a class (or a role) and knows about its invocant, i.e., the object that called it and its class. And, of course, it has a different calling syntax. Another important difference between a subroutine and a method is that, since there may be several methods with the same name defined in different classes (or different roles), a method invocation involves a dispatch phase, in which the object system selects which method to call, usually based on the class or type of the invocant. However, in Perl 6, that difference is blurred by the fact that you can have multi subroutines, i.e., subroutines with the same name and a different signature that are also resolved at run time, depending on the arity (number of arguments) and type of the arguments. Within a method definition, self refers to the
invocant, the object that invoked the method.
There is a short hand for it, method coordinates { # accessor to both x and y return ($.abscissa, $.ordinate) } The two syntax formats, There is a third syntactic way of doing it, using an exclamation mark instead of a dot: method coordinates { # accessor to both x and y return ($!abscissa, $!ordinate) } Here, the result would be the same, but this new syntax is
not equivalent: We can now create an object and call our methods on it: my $point = Point2D.new( abscissa => 4, ordinate => 3 ); say $point.coordinates; # -> (4 3) say $point.distance2center; # -> 5 say $point.polar-coordinates; # -> (5 0.643501108793284)
You might remember from previous chapters that if you use a method
without naming an explicit invocant, then the method applies to
the .say for <one two three>; # -> one two three (each on one line)
Now that we have created an object with some methods, we can also
take advantage of the same syntax shortcut. For example if we
use for or given to populate the given $point { say .coordinates; # -> (4 3) say .distance2center; # -> 5 .polar-coordinates.say; # -> (5 0.643501108793284) } As an exercise, you could write a method called
The methods of our class so far are all accessors, which
means they provide a snapshot of some of the invocant’s attributes.
If the attributes are mutable (declared with the class Point2D-mutable { has Numeric $.abscissa is rw; has Numeric $.ordinate is rw; # perhaps the same accessors as in the class definition above method new-ordinate (Numeric $ord) { self.ordinate = $ord; } } # Creating the Point2D-mutable object: my $point = Point2D-mutable.new(abscissa => 3, ordinate => 4); say $point; # -> Point2D-mutable.new(abscissa => 3, ordinate => 4) # Modifying the ordinate: $point.new-ordinate(6); say $point; # -> Point2D-mutable.new(abscissa => 3, ordinate => 6) 12.5 Rectangles and Object CompositionSometimes it is obvious what the attributes of an object should be, but other times you have to make decisions. For example, imagine you are designing a class to represent rectangles. What attributes would you use to specify the location and size of a rectangle? You can ignore angle; to keep things simple, assume that the rectangle’s edges are either vertical or horizontal. There are at least two possibilities:
At this point it is hard to say whether either is better than the other, so we’ll implement the first one, just as an example. Here is the class definition: class Rectangle { has Numeric $.width; has Numeric $.height; has Point2D $.corner; # lower left vertex method area { return $.width * $.height } method top-left { $.corner.abscissa, $.corner.ordinate + $.height; } # other methods, e.g. for other corners' coordinates, center, etc. } The new feature compared to the previous Point2D class
definition is that the The top-left method returns the coordinates of
the top left angle of the rectangle. This top-left
method gives us an opportunity to explain a bit more
the difference between the method top-left { $!corner.abscissa, $!corner.ordinate + $!height; } But it would not be possible to use We can now create a Rectangle object: my $start-pt = Point2D.new(abscissa => 4, ordinate => 3); my $rect = Rectangle.new(corner => $start-pt, height => 10, width => 5); say "top-left coord.: ", $rect.top-left; # -> top-left coord.: (4 13) say "Rectangle area: ", $rect.area; # -> Rectangle area: 50 You might have noticed that the arguments passed to the Rectangle.new constructor are not in the same order as in the class definition. I did that on purpose to show that the order is unimportant because we are using named arguments. Figure ?? shows the state of this object.
Using an object as an attribute of another object, possibly of another class, is called object composition. An object that is an attribute of another object is embedded. Object composition makes it possible to easily define nested layers of abstraction and is a powerful feature of object-oriented programming. In our “geometry” example, we started to define a low-level object, a Point2D instance, and then used that point to build a higher level type, Rectangle. 12.6 Instances as Return ValuesMethods can return instances of another class. For example, the Rectangle class can have methods returning instances of Point2D for the other corners: method top-right-point { return Point2D.new( abscissa => $!corner.abscissa + $!width, ordinate => $!corner.ordinate + $!height ); } # other methods for other corners Notice that we don’t even bother to give a name to upper right point (although we could, if we wanted); we create it with the constructor and return it on the fly. We can use the new method as follows: my $topRightPt = $rect.top-right-point; say "Top right corner: ", $topRightPt; # -> Top right corner: Point2D.new(abscissa => 9, ordinate => 13)
Although this is not very useful in such a simple case, we
could play it safe and declare a Point2D type for
my Point2D $topRightPt = $rect.top-right-point; This way, the code will raise an error if the top-right-point happens to return something other than a Point2D instance. Similarly, the method find-center { Point2D.new( abscissa => $!corner.abscissa + $!width / 2, ordinate => $!corner.ordinate + $!height / 2 ); } This new method can be used as follows: say "Center = ", $rect.find-center; # -> Center = Point2D.new(abscissa => 6.5, ordinate => 8.0) 12.7 InheritanceInheritance is probably the most emblematic feature of object-oriented programming. It is a mechanism through which it is possible to derive a class from another class. Inheritance is one of the standard ways to implement code reuse in object-oriented programming. It is also another useful way of defining successive layers of abstraction and a hierarchy of types. 12.7.1 The Pixel ClassThe Point2D class is very general and could be used for a variety of purposes: geometry, vector graphics, animated mangas, and so on. We may want to use it to display graphic data on a screen. For this scenario, let’s create a new derived class, Pixel, adding new properties to the point, such as color, perhaps transparency, etc. Do we need to redefine all the attributes and methods for the new class? No, we don’t. We can define a new class that inherits the properties of the Point2D base class and only modify what is no longer suitable or add whatever new features we need. Here, we want a new attribute to represent the pixel color and probably some new methods dealing with this new attribute. According to the most common standards, a color is defined by three integers (really three octets, i.e., integers between 0 and 255 in decimal notation), representing the red, green, and blue (RGB) components of the pixel: class Pixel is Point2D { has %.color is rw; method change_color(%hue) { self!color = %hue } method change_color2(Int $red, Int $green, Int $blue) { # signature using positional parameters self!color = (red => $red, green => $green, blue => $blue) } } The new class inherits the properties of Point2D thanks to the is Point2D trait, except possibly those that are explicitly modified (or overridden) or added in the new class. The new class is sometimes called a child class or subclass, whereas Point2D is the parent class. Creating this new class based on Point2D is called subclassing the Point2D parent class. The new child class inherits the abscissa and ordinate attributes of the Point2D parent class (and their specific type and properties if any), as well as the methods such as coordinates defined in the parent class. The child class has a new attribute (the color) and two new methods. In the preceding code example, we have written two different methods for changing the color only to illustrate two possible syntax formats, for pedagogical purposes. The first one receives a hash as a parameter, and the second one uses positional parameters, which forces the user to remember the order (RGB) in which the arguments must be passed; this can be a source of error and should be avoided when the number of parameters exceeds a certain limit (which will be left up to the reader). On the other hand, anyone working commonly with graphics knows by heart the standard conventional order of colors (i.e., RGB). Also, the second method has the advantage of enabling some type checks (the arguments must be integers). This is a simplified example; in real life, it may be desirable to check that the parameters are octets, i.e., integers between 0 and 255 (which could be done by adding a type constraint or defining a subset of the integer type). Using the new Pixel class is straight forward: say "Original colors: ", $pix.color; $pix.change_color({:red(195), :green(110), :blue(70),}); say "Modified colors: ", $pix.color; say "New pixel caracteristics:"; printf \tAbscissa: %.2f\n\tOrdinate: %.2f\n\tColors: R: %d, G: %d, B: %d\n", $pix.abscissa, $pix.ordinate, $pix.color<red>, $pix.color{"green"}, $pix.color{"blue"}; $pix.change_color2(90, 180, 30); # positional args say "New colors: \tR: {$pix.color<red>}, G: {$pix.color<green>}, B: {$pix.color<blue>} "; This displays the following output: Original colors: {blue => 145, green => 233, red => 34} Modified colors: {blue => 70, green => 110, red => 195} New pixel caracteristics: Abscissa: 3.30 Ordinate: 4.20 Colors: R: 195, G: 110, B: 70 New colors: R: 90, G: 180, B: 30 To tell the truth, it was not necessary to use two different
method names, multi method change_color(%hue) { self.color = %hue } multi method change_color(Int $red, Int $green, Int $blue) { # signature using positional parameters self.color = (red => $red, green => $green, blue => $blue) } Since the multi method is defined twice, with the same name but with a different signature, the object system is able to dispatch the invocation to the right method. 12.7.2 The MovablePoint ClassThe Suppose, however, that our application is about kinematics (the branch of physics dealing with the motion of points or bodies) or is a video game. In such a case, we probably want our points (or sets of points) to move. We need a new class, MovablePoint, enabling the modification of coordinates. We don’t need to redefine all the attributes and methods for the new class. Again, we can define a new class that inherits the properties of the Point2D base class and only modifies what is no longer suitable or adds whatever new features we need, for example: class MovablePoint is Point2D { has Numeric $.abscissa is rw; has Numeric $.ordinate is rw; method move (Numeric $x, Numeric $y) { $.abscissa += $x; $.ordinate += $y; } } The new class inherits the properties of Point2D thanks to the is Point2D trait, except those that are explicitly modified (or overridden) or added in the new class. Methods that exist in the parent class and are redefined in a child class are said to be overridden within that class. Here, the Note that we have used positional parameters here for the
move method. We said that it is often better for the sake of clarity
to use named parameters, but we have only two parameters here;
as it is fairly simple to remember that the We can now test our new child class, create a MovablePoint instance, display its characteristics, move it to a different location, and display the new position: my $point = MovablePoint.new( abscissa => 6, ordinate => 7, ); say "Coordinates : ", $point.coordinates; say "Distance to origin: ", $point.distance2center.round(0.01); printf "%s: radius = %.4f, theta (rad) = %.4f\n", "Polar coordinates", $point.polar-coordinates; say "--> Moving the point."; $point.move(4, 5); say "New coordinates: ", $point.coordinates; say "Distance to origin: ", $point.distance2center.round(0.01); printf "%s: radius = %.4f, theta (rad) = %.4f\n", "Polar coordinates", $point.polar-coordinates; This produces the following output: Coordinates : (6 7) Distance to origin: 9.22 Polar coordinates: radius = 9.2195, theta (rad) = 0.8622 --> Moving the point. New coordinates: (10 12) Distance to origin: 15.62 Polar coordinates: radius = 15.6205, theta (rad) = 0.8761
Here, when the user code invokes the coordinates,
distance2center, and polar-coordinates methods,
Perl finds that they do not exist in MovablePoint. But,
as 12.7.3 Multiple Inheritance: Attractive, but Is It Wise?In object-oriented programming, the inheritance mechanism is a traditional way to reuse code, it is even probably the most common way to do it. A class may have several parent classes and, thus, subclass several other classes. This is what is called multiple inheritance. We might want to build a new MovablePixel class inheriting from both MovablePoint and Pixel (and, indirectly, from Point2D). Technically, you can easily do it in Perl: class MovablePixel is MovablePoint is Pixel { # ... } Now, MovablePixel is subclassing both MovablePoint and Pixel and inheriting from both parent classes. This looks very promising, but it turns out to be more complicated than expected in real situations. If there is a conflict (for example a name collision between two methods), which one shall prevail? Some mechanisms exist to handle such situations (for example in the C++ programming language), and Perl has some metaobject methods to find out about the method resolution order (MRO), but this might quickly leads to severe design problems and to really subtle or complicated bugs. In short, while multiple inheritance originally looked as a attractive idea, it turned out to be complicated to master, because it creates multiple and often implicit dependencies that are quite hard to sort out. This is the reason why, contrary to C++, relatively more recent OO programming languages such as Java (which came out not so recently, back in 1995) have decided not to implement multiple inheritance. Perl 6 does not want to forbid such things and allows you to use multiple inheritance if you wish, and it can be useful for simple cases; so don’t necessarily rule it out, but remember that, contrary to early expectations, it often leads to a mess and turns out to be quite unmanageable. Perl offers better concepts for tackling such situations, as we will see shortly. 12.8 Roles and CompositionInheritance is a very powerful concept to describe a hierarchical tree of concepts. For example, you can think of a hierarchy of geometrical figures having more and more specific properties:
It is relatively easy to imagine a series of classes with a hierarchical inheritance tree reflecting those properties. It gets slightly more complicated, however, if we add the rhombus (a parallelogram with all sides equal), because the square is now also a rhombus with four right angles. The square class would subclass both the rectangle and the rhombus, and we might have here a possible multiple inheritance issue. Similarly, we can think of a tree of classes with nested inheritance representing various types of numbers (e.g. integer, rational, real, complex) or animals species (e.g., vertebrate, mammal, carnivoran, canid, dog, Irish setter). These are great examples for inheritance, but the real world is rarely so hierarchical, and it is often difficult to force everything to fit into such a hierarchical model. This is one of the reasons why Perl introduces the notion of roles. A role is a set of behaviors or actions that can be shared between various classes. Technically, a role is a collection of methods (with possibly some attributes); it is therefore quite similar to a class, but the first obvious difference is that a role is not designed to be instantiated as an object (although roles can be promoted into classes). The second difference, perhaps more important, is that roles don’t inherit: they are used through application to a class and/or composition. 12.8.1 Classes and Roles: An ExampleLet’s come back to vertebrates, mammals and dogs. A dog is a mammal and inherits some characteristics from the mammals, such as having a neocortex (a region of the brain), hair, and mammary glands, as well as a vertebral column, which all mammals (along with fishes, birds, reptiles, and others) inherit from vertebrates. So far, the class hierarchy seems simple and natural. But dogs can have very different characteristics and behaviors. To quote the Wikipedia article on dogs: “Dogs perform many roles for people, such as hunting, herding, pulling loads, protection, assisting police and military, companionship and, more recently, aiding handicapped individuals” (italic emphasis added). Dogs can also be feral animals (i.e., animals living in the wild but descended from domesticated individuals) or stray dogs. All these additional behaviors might be added to the dog class. Similarly, a cat, another mammal, may also be a pet or a feral animal. Mustangs, North American free-roaming horses, are also feral animals, descended from once-domesticated horses; but a mustang may also be captured and brought back to domesticated state. This return to the wild of feral animals is not limited to mammals: pigeons living in our cities are often descended from once-domesticated homing pigeons used in the past. It can even happen with invertebrates, such as swarms of honey bees. It is apparent that a hierarchical modeling of inheritance trees is not adapted to describe such behaviors. We can define classes for dogs, cats, and horses as subclasses of mammals (which itself inherits from vertebrates). Besides that, we define roles for pet or feral animals. In addition, we can create new classes subclassing the dog, horse, and cat classes and doing some specific roles; or we can assign roles to individual instances of a class. This could look like this (this is a dummy example that cannot be tested): class Vertebrate { method speak {say "vertebrate"};} class Mammal is Vertebrate { method speak { say "mammal" } } class Bird is Vertebrate { method fly {} } class Dog is Mammal { method bark {} } class Horse is Mammal { method neigh {} } class Cat is Mammal { method meow {} } class Mouse is Mammal { method squeek {} } class Duck is Bird { method quack {} } # ... role Pet-animal { method is-companion() {...} # other methods } role Shepherd { ... } # sheep keeper role Feral { ... } # animal back to wild life role Guide { ... } # blind guide role Human-like { ... } # animal behaving like a human # ... class Guide-dog is Dog does Guide { ... } class Shepherd-dog is Dog does Shepherd { ... } class Stray-dog is Dog does Feral { ... } class Pet-cat is Cat does Pet-animal { ... } class Feral-cat is Cat does Feral { ... } class Mustang is Horse does Feral { ... } class Domestic-canary is Bird does Pet-animal { ... } # ... # Role can also be applied to instances: my $garfield = Pet-cat.new(...); my $mickey = Mouse.new(...); $mickey does Human-like; my $donald = Duck.new(...); $donald does Human-like; my $pluto = Dog.new(...); $pluto does Pet-animal; my $snoopy = Dog.new(...); $snoopy does Pet-animal does Human-like; A role is applied to a class or an object with the does trait (as opposed to is for inheritance). These different keywords reflect the semantic difference associated to them: composing a role into a class or an object provides this class or object with the supplementary behavior associated with the role, but it does not follow that the object receiving the role is the same thing as or of the same nature as the role. If the Pet-animal and feral roles had been defined as classes, then the Pet-cat and Feral-cat classes would have undergone double inheritance, with the potential problems associated with that. By applying a role to a class, you avoid constructing a multiple-inheritance tree that is probably not really justified and can be complicated to conceptualize and difficult to maintain. Judicious use of classes and roles can lead to a model that is simpler, more natural, and closer to the real relations between the entities and behaviors under scrutiny. In addition, if you inadvertently compose several roles with two methods having the same name, this immediately raises an error (unless a method of the same name exists within the class, in which case it prevails), rather than dispatching silently to one of the two methods as in the case of multiple inheritance. In that case, naming conflicts are identified immediately (at compile time), which has the benefit of immediately finding a bug that might otherwise go unseen for a while. 12.8.2 Role Composition and Code ReuseClasses are meant for managing instances and roles are meant for managing behaviors and code reuse. The following example shows how classes and roles can play together. role Drawable { has $.color is rw; method draw { ... } } class Figure { method area { ... } } class Rectangle is Figure does Drawable { has $.width; has $.height; method area { $!width * $!height; } method draw() { for 1..$.height { say 'x' x $.width; } } } Rectangle.new(width => 10, height => 4).draw;
Please note that the ellipsis The code example above draws an ASCII rectangle: ~ perl6 test_drawable.pl6 xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx 12.8.3 Roles, Classes, Objects, and TypesA role can be applied to an entire class or only to some instances of the class: role Guide { ...} class Guide-dog is Dog does Guide { ... } # Composing the Guide role into the Guide-dog class # inheriting from the Dog class my $doggy = new Dog; # creating a Dog object $doggy does Guide; # applying the role to the object Roles and classes are different, but both are or define types. This means that a role can be used as a type for a variable declaration where you might expect a class name. For example, the Guide role sketched in the code snippet above does effectively create a Guide type. So a Blind role for a human might have an attribute of Guide type, which might represent a guide-dog, a guide-horse, a human guide, or even a guiding robot. class Human { has Dog $dog; # May contain any dog, with or without # a guide role } role Blind { has Guide $guide; # May contain any Guide type, whether # a dog, a horse, a human or a robot } A number of Perl 6 built-in types are defined by roles and not by classes, such as IO, Iterable, Iterator, Numeric, Rational, Real, etc. 12.9 Method DelegationDelegation is another way to link an object to another piece of code. The delegation technique has been relatively well studied at the theoretical level and implemented in a few specialized research languages, but mainstream generalist languages implementing delegation are rather rare. Rather than defining methods in a class or in a role, the idea is to invoke methods belonging to another object, as if they were methods of the current class. In Perl 6, delegation may be performed at the level of a class or a role. A delegated object is simply an attribute defined in the class or in the role with the handles keyword which makes it possible to specify which methods of the delegated object may be used in the current class: class BaseClass { method Don-Quijote() { "Cervantes" } method Hamlet() { "Shakespeare" } method Three-Sisters () { "Chekhov" } method Don-Carlos() { "Schiller" } } class Uses { has $.base is rw handles < Don-Quijote Hamlet Three-Sisters >; } my $user = Uses.new; $user.base = BaseClass.new(); # implementing an object-handler say $user.Don-Quijote; say $user.Hamlet; say $user.Three-Sisters; say $user.Don-Carlos; This displays the following output: Cervantes Shakespeare Chekhov Method 'Don-Carlos' not found for invocant of class 'Uses' in block <unit> at delegate.pl6 line 16 The program properly displays the names of writers returned by the first three methods, because they have been sort of “imported” into the Uses class, but it fails on the last one, because “Don-Carlos” is not part of the handler’s list. The error on the last method is a runtime exception and the program would stop running there even if there were some more correct code afterward. Note that the Uses class does not know from where the
methods will be imported; it only knows about the names of
the methods that will be imported. It is only when the
my $user = Uses.new( base => BaseClass.new() ); There is no need to enumerate the methods to be handled. The Uses class can import all the methods of BaseClass: class Uses { has $.base is rw handles BaseClass; } This will work as before, except of course that it will not fail on the Don-Carlos method this time, since this method is also imported now: Cervantes Shakespeare Chekhov Schiller 12.10 PolymorphismPolymorphism is a way to supply a common or close interface to different types. In a certain way, the inheritance examples studied previously offer a form of polymorphism: the coordinates, distance2center, and polar-coordinates methods are polymorphic, since they can apply to Point2D, movablePoint, and pixel types. But these are trivial forms of polymorphism. We will speak of polymorphism when the relevant methods or functions are doing something different from each other, at least at the implementation level, even if they share the same name and interface. Outside of object-oriented programming, Perl’s multi subroutines implement a form of polymorphism, since they can behave differently depending on the type and number of their arguments. Within the OOP context, it is often the type of the invocant (its class or possibly one of its roles) that will determine, usually at runtime, which of the possible methods will be invoked. For example, we might want to create a new class for points in a three-dimensional space. The methods will have to be different, but it seems interesting to offer the user an interface that is the same (or almost) as for two-dimensional points: class Point3D { has Numeric $.x; has Numeric $.y; has Numeric $.z; method coordinates () { # accessor to the 3 coordinates return $.x, $.y, $.z } method distance2center () { return ($.x ** 2 + $.y ** 2 + $.z ** 2) ** 0.5 } method polar-coordinates () { return self.spherical-coordinates; } method spherical-coordinates { my $rho = $.distance2center; my $longitude = atan2 $.y, $.x; # theta my $latitude = acos $.z / $rho; # phi return $rho, $longitude, $latitude; } method cylindrical-coordinates { # ... } } The methods in this new class are not the same as those in Point2D, but methods with a similar semantics have the same name; it is thus possible to use either class without being lost with different names. The distance2center method has exactly the same interface. The coordinates method returns a list of three values instead of two, but the calling convention is the same. Note that it might also have been possible to design Point2D so that this method would return a third zero value, in order to have exactly the same interface (after all, a point in the plane might be considered as a point in the 3D space with a zero height); complying to exactly the same interface is not mandatory, but only a possible implementation decision that might make for a more intuitive interface.
The notion of polar coordinates does not have a well-defined
meaning in a 3D space, but I have chosen here to keep the name
in our interface because it is intuitively quite similar to
the idea of spherical coordinates; it does nothing more
than invoke the Please note that mathematicians, physicists, astronomers, engineers, geographers, and navigators all use the same basic system for spherical coordinates, but their conventions are different concerning the origin, angle range, angle measurement units and rotation direction, and the name of the various values or symbols associated with them. So you might find some different formulas in a textbook. The conventions and formulas we have used here are commonly used in geography and some branches of mathematics. A real general-purpose class might have to take these varying conventions into account and implement the necessary conversions. 12.11 EncapsulationEncapsulation is the idea of hiding the data and the code of a library or a module from the user. It is not specific to object-oriented programming, but it is a fundamental concept of OOP. In object-oriented programming, encapsulation consists of protecting the data in an object from being tampered with directly (and possibly made inconsistent) by the user, who can access such data only through the means of methods. This is achieved by providing to the user methods that are commonly called accessors (or getters) and mutators (or setters). This makes it possible to ensure that the object properties will be validated by its methods. Encapsulation is a strong form of data abstraction and procedural abstraction. Seen from the outside, an object is a black box having some specified properties and behaviors. This way, these properties and behaviors are hidden from the user. They’re not hidden in the sense that the user cannot know about them (at least in the open-source world, it is easy to know that), but hidden in the sense that it is usually not possible to use that knowledge to bypass the supplied interface. This means that the internal implementation of the object may change without having to modify the external behavior. If you are going to use insider knowledge, your code will probably break when the internal implementation is modified, so don’t do that. Various programming languages don’t have the same rules for guaranteeing encapsulation. Some are stricter than others, some are less restrictive for read access than for write access, others don’t make such a distinction but rather rely on the visibility level specified for an attribute, for example “public” or “private” (with sometimes an intermediate “protected” level). Perl 6 lets you choose the encapsulation model you want to apply to your objects and attributes. All attributes are private. If you declare a class as follows: class Point2D { has $!abscissa; has $!ordinate; # … method value_x { return $!abscissa } method value_y { return $!ordinate } } the But as we have seen earlier, if you declare this class as follows: class Point2D { has $.abscissa; has $.ordinate; # ... } the coordinates will still be private attributes, but Perl 6 will automatically generate accessor methods having the same names as the attributes, so that it will be possible to access them from outside the class almost as if they were public: class Point2D { # ... } my $point = Point2D.new(abscissa => 2, ordinate => 3); say $point.abscissa; # -> 2 Whether the attribute is mutable or not is managed separately by the is rw trait. In brief, Perl 6 offers a default access mode, but you can fine-tune it and what you need. 12.11.1 Private MethodsMethods are the normal way to use objects, whether with read-only or read and write access. They usually form the interface of a class, that is the part of the class that is made public and available to programmers wishing to use them. It is thus natural and legitimate for methods to be public, i.e., accessible from outside the class. But a class may also contain numerous methods that are part of the internal cooking recipes of the class, i.e., the way it does things internally, and that are not meant to be used from outside the class. It is possible to prevent their use from outside the class by making these methods private. A Perl 6 private method is prefixed with an exclamation mark: method !private-behavior($x, $y) { ... } You will also need to use an exclamation mark to call them: $my-object!private-behavior($val1, $val2) Private methods are really internal to a given class. In particular, they are not inherited by child classes. 12.11.2 Constructing Objects with Private AttributesConstructing objects with private attributes raises a little difficulty. Let’s consider the following program: class Point3D { has $.x; has $.y; has $!z; method get { return ($!x, $!y, $!z); } }; my $a = Point3D.new(x => 23, y => 42, z => 2); say $_ for $a.get;
In this example, we have declared 23 42 (Any) Oops, what is going on? It seems that the get
method is not able to read The guilt lies with the new implicit constructor which, by default, initializes only “public” attributes. Here, the simplest solution is probably to add a BUILD submethod in the class definition. A submethod is a public method of a class that is not inherited in its child classes. Semantically, it is really equivalent to a subroutine, but it is called with a method syntax (hence the name). Submethods are especially useful to perform object construction and destruction tasks that should not be inherited by subclasses, as well as for tasks that are so specific to a given type that classes derived from it will almost surely have to redefine them. Initializing private attributes at object instantiation might look like this: class Point3D { has $.x; has $.y; has $!z; submethod BUILD (:$!x, :$!y, :$!z) { say "Initialization"; $!x := $!x; $!y := $!y; $!z := $!z; } method get { return ($!x, $!y, $!z); } }; my $a = Point3D.new(x => 23, y => 42, z => 2); say $_ for $a.get; The program now works as desired and displays all three attributes: Initialization! 23 42 2 This works because the default new constructor, a method defined in the Mu ultimate superclass and inherited by default by any Perl 6 class, calls the default BUILD submethod. If we redefine BUILD in our class, it will supersede the default one called by new. By redefining BUILD, we force the constructor to take into account the private attribute that was not used previously. Quite a bit of simplification is possible. Since passing arguments to a routine binds the arguments to the parameters, a separate binding step is unnecessary if the attributes are used as parameters. Hence, the BUILD submethod in the example above could also have been written simply as: submethod BUILD(:$!x, :$!y, :$!z) { say "Initialization!"; } While we are speaking about the intricacies of object construction, note that since new is a method inherited from the Mu superclass, you can override it if you wish. The default new constructor can only be used with named arguments. Assuming you absolutely want to use positional parameters, you could override new with your own method, like so: class Point2D { has Numeric $.abscissa; has Numeric $.ordinate; method new ($x, $y) { self.bless(abscissa => $x, ordinate => $y); } method coordinates { # accessor to both coordinates return (self.abscissa, self.ordinate) } # other methods }; my $point = Point2D.new(3, 5); say $_ for $point.coordinates; This will duly display the two coordinates. bless is a low-level method for object construction, inherited from Mu and called automatically when you invoke new to construct an object. You usually don’t need to know about it, except when you want to write your own custom constructor. You can give the constructor a different name than new, for example: class Point2D { has Numeric $.abscissa; has Numeric $.ordinate; method construct ($x, $y) { self.bless(abscissa => $x, ordinate => $y); } method coordinates { # accessor to both coordinates return (self.abscissa, self.ordinate) } # other methods }; my $point = Point2D.construct(3, 5); say $_ for $point.coordinates; Think twice, though, before you override new or create your own custom constructor with a different name, as it may make it more complicated to subclass your Point2D class. 12.12 Interface and ImplementationOne of the goals of object-oriented design is to make software more maintainable, which means that you can keep the program working when other parts of the system change, and modify the program to meet new requirements. A design principle that helps achieve that goal is to keep interfaces separate from implementations. For objects, that means that the public interface of the methods provided by a class should not depend on how the attributes are represented. For example, we designed a Point2D class in which the main attributes were the point’s Cartesian coordinates. We may find out that, for the purpose of our application, it would be easier or faster to store the point’s polar coordinates in the object attributes. It is entirely possible to change the internal implementation of the class, and yet keep the same interface. In order to do that, we would need the constructor to convert input parameters from Cartesian into polar coordinates, and store the latter in the object attribute. The polar-coordinates method would return the stored attributes, whereas methods returning the Cartesian coordinates may have to do the backward conversion (or may perhaps be stored separately in private attributes). Overall, the change can be made with relatively heavy refactoring of the Point2D class, but users of the class would still use the same interface and not see the difference. After you deploy a new class, you might discover a better implementation. If other parts of the program are using your class, it might be time-consuming and error-prone to change the interface. But if you designed the interface carefully, you can change the implementation without changing the interface, which means that other parts of the program don’t have to change. 12.13 Object-Oriented Programming: A TaleMost tutorials and books teaching object-oriented programming tend to focus on the technical aspects of OOP (as we have done in this chapter so far), and that’s a very important part of it, but they sometimes neglect to explain the reasons for it. They say “how,” but not “why.” We’ve tried to explain the “why” (and hopefully succeeded in doing so), but this section attempts to explain OOP from the standpoint of the reasons for it and its benefits, independently of any technical consideration, in the form of a parable (the code examples are only pseudocode and are not supposed to compile, let alone run). 12.13.1 The Fable of the ShepherdOnce upon a time, there was a sheep farmer who had a flock of sheep. His typical workday looked like this: $shepherd.move_flock($pasture); $shepherd.monitor_flock(); $shepherd.move_flock($home); Eventually, due to successful wool sales, he expanded his farming activities and his day became like this: $shepherd.move_flock($pasture); $shepherd.monitor_flock(); $shepherd.move_flock($home); $shepherd.other_important_work(); But now the shepherd wanted to devote more time to
$shepherd-boy.move_flock($pasture); $shepherd-boy.monitor_flock(); $shepherd-boy.move_flock($home); $shepherd.other_important_work(); This did give the shepherd more time for
$sheep-dog.move_flock($pasture); $sheep-dog.monitor_flock(); $sheep-dog.move_flock($home); $shepherd.other_important_work();
12.13.2 The MoralWe can learn a few things from this parable. 12.13.2.1 DelegationTo handle complexity, delegate to a suitable
entity, e.g., the farmer delegates some of his
work to 12.13.2.2 EncapsulationTell objects what to do, rather than micro-manage, e.g.: $sheep-dog.monitor_flock(); rather than something like: $sheep-dog.brain.task.monitor_flock; At a high level, we do not particularly care what the internals of the object are. We only care what the object can do. An object becomes harder to change the more its internals are exposed. 12.13.2.3 Polymorphism
The fable of this section is adapted from a post by “Arunbear” on the “PerlMonks” website: http://www.perlmonks.org/?node_id=1146129. Thanks to “Arunbear” for authorizing me to reuse it. 12.14 DebuggingThis section is about using a debugger, a program that is designed to help you to debug your programs. “What? There is a tool to debug my programs, and you’re telling me only now?” you might complain. Well, it’s not quite that. A debugger is not going to do the debugging for you; you’ll still have to do the hard investigation work, but a debugger can help you a lot in figuring out why your program isn’t doing what you think it should be doing. Or, rather, why what your program is doing isn’t quite what you want it to do. Debuggers are a bit like people with a strong personality: some people love them and others hate them. Often, people who don’t like debuggers simply never took the time to learn how to use them, but there are also many expert programmers who don’t like them and whom we can’t suspect of not having seriously tried. Whether you like debuggers or not is probably a matter of personal taste, but they can provide an invaluable help, if you know how to use them. 12.14.1 The Perl 6 DebuggerRakudo-Perl 6 ships with an interactive debugger that you call with the perl6-debug command (or, on some installs at least, perl6-debug-m). You can just fire this command, followed by the name of the program to be debugged, just as you would normally use perl6 with the name of a program to run the program. One word of warning: you can run the debugger on a program only if the program compiles with no errors; a debugger is not aimed as finding compile-time error, but only execution or semantic errors. Once you’ve launched the debugger, you will see something like this: >>> LOADING while_done.pl6 + while_done.pl6 (1 - 3) | while True { | my $line = prompt "Enter something ('done' for exiting)\n"; | last if $line eq "done"; >
This says that it is loading the 12.14.2 Getting Some HelpThe first command you probably want to issue is “h,” which will display the debugger help and return to the prompt. Below, we have omitted most of the output for brevity: > h <enter> single step, stepping into any calls s step to next statement, stepping over any calls so step out of the current routine [...] q[uit] exit the debugger > Take the time to issue that command and to read the various possible instructions you can enter. We will describe the most common ones. As you can see above, just use “q” or “quit” to exit the debugger. 12.14.3 Stepping Through the CodeThe main characteristic of a debugger is that it lets you run the program step by step. Each time you hit the Enter key, the program will move forward one step (e.g., one code line). It will enter into any subroutine if the code line is a subroutine call, but you can step over the subroutine call by issuing the “s” command at the debugger prompt: this will run the subroutine and bring you to the first code line after the subroutine call (and any nested call of other subroutines) is over. If you entered into a subroutine but are no longer interested in stepping through it, just issue the “so” command to step out of it. At any point through that process, you can look at the content of variables or even call methods on them. To view a variable, just type its name and then press Enter: > $line "foo" You can also view an array or a hash, or use the index
or the key, for example You may also use “s” (or “say”) or “p” (or “print”) to evaluate and display an expression in the current scope. 12.14.4 Stopping at the Right Place with BreakpointsYou might find it tedious to run through the program step by step until you get to the interesting part. As it happens, you can get there immediately using a breakpoint. For adding a breakpoint, you type bp add line, where line is the line number where you want the program to stop running and resume stepping line by line. Then you enter the “r” command and the program will run until it reaches one of the breakpoints that you have set. The execution will also stop if the program runs into an exception; in that case, you can still access variables to try to figure out what went wrong. If it does not hit a breakpoint or an exception, it will run to the end. You can view all breakpoints (bp list), remove one breakpoint (bp rm line), or remove all breakpoints (bp rm all). You can also set a breakpoint in another file (for example if you are using a module) by using the following syntax: bp add file:line, where “file” is the file name. 12.14.4.1 You’re all set to start using the debuggerYou probably know enough by now to make good use of the Perl 6 debugger, step through your program and find out where it does something that isn’t what you intended. It wasn’t so much to learn, was it? Try it! We’ll cover a couple of additional goodies, though. 12.14.5 Logging Information with Trace Points
It is possible to set trace points on specific lines of code
and variables (or expressions), with the command tp add
line $var. This will record the value of For example, we used it to log the variable > tp show >>> rotate.pl6:23 * * Z * ZA * ZAC * ZACB * ZACBz * ZACBza * ZACBzab 12.14.6 Stepping Through a Regex MatchThe debugger can also provide useful information when the code is trying to match a regex. For example, suppose we’re running a program under the debugger in which we have the following code: "foobar" ~~ /f.+b/; If you run the regex step by step, color highlighting will show atom by atom where it is in the regex and which part of the string has been matched. (We can’t show the color highlighting here, but you should try it to see it.) With the above regex, you’ll see that the regex engine tries to match the “f” of the pattern and that it finds an “f” at the beginning of the string; next, you’ll see that the regex engines tries to match the “.+” subpattern and that it matches the whole string; then, when the regex engine tries to match the final “b” of the pattern, you’ll see that the regex engine backtracks and gives away the “r” and then the “a”; finally, the regex engine succeeds with “foob.” If you have difficulty understanding how regexes work or are mystified by backtracking, just run the debugger on a few regexes and observe what’s going on step by step. You don’t even have to write a program; you can use it as a one-liner. For example, to test the above regex as a one-liner under Windows, just type the following command at the prompt: C:\Users\Laurent>perl6-debug-m -e "'foobar' ~~ /f.+b/;" As usual, change double quotes to single quotes and the other way around if you are using a Unix-like platform. Our final word on the debugger: remember you can always hit “h” to get help on the command you need. 12.15 Glossary
|
Are you using one of our books in a class?We'd like to know about it. Please consider filling out this short survey.
|