2020/12/19

2020 Perl Advent Calendar - Day 19

<< First | < Prev | Next >

We have already discussed that the most fundamental property of an object-oriented programming is the idea that a collection of state can be encapsulated into a single piece, and given behaviours that operate on the state. In yesterday's article we saw how to create new classes of object (with the class keyword), and how to add behaviours (with the method keyword). Today we'll take a closer look at the other half of this - how to add state.

While the word "method" seems to be fairly well entrenched, various object systems across various languages have a variety of different words to describe the state values stored for each given instance. The word "field" has been used in Perl before, and refers specifically to the now-obsolete fields pragma. Sometimes programmers refer to "attributes" of an object, but in Perl this is also an overloaded term referring to the :named annotations that can be applied to functions or variables. In Object::Pad the per-instance state is stored in variables called "slots".

Within a class, slots are created by the has keyword. This looks and feels similar to the my and our keywords. It introduces a new variable, optionally initialised with the value of an expression. Whereas a my or our variable is visible to all subsequent code (including nested functions) within its scope, a has variable is only visible within functions declared as method, because it will be associated with individual instances of the object class.

In this example the slot variables storing the label and click behaviour are available within any method:

class Tickit::Widget::Button extends Tickit::Widget;

has $_label;
has $_on_click;

method label { return $_label; }

method set_label
{
    ( $_label) = @_;
    $self->redraw;
}

method on_click { return $_on_click; }

method click
{
    $_on_click->($self);
}

In terms of visibility these slot variables behave much like other kinds of lexical variable - namely, they are not visible from outside the source of this particular class. This means that by default any such state variables are private to the class's implementation, inaccessible by other code that uses the class. We can choose to expose certain parts of it via the class's interface by providing these accessor methods, but we are not required to do so.

It is a common style in Object::Pad-based code to name the slot variables with a leading underscore, as in this example, as it helps them to stand out visually in larger code. It helps remind people that these are slot variables, because they now lack other visual signalling (such as $self->{...}) to otherwise distinguish them.

Another common behaviour is creating simple accessor methods to simply return the value of a slot, thus deciding to expose that particular variable as part of the object's interface, visible to callers. So common in fact that Object::Pad provides a shortcut to create these accessor methods automatically:

class Device::Chip::SSD1306 extends Device::Chip;

has $_rows :reader;
has $_columns :reader;

# now the class has ->rows and ->columns methods visible

The :reader attribute requests that a simple accessor method is created to return the current value of the slot. It is named the same as the slot, with a leading underscore first removed to account for the common naming convention.

One key advantage that these variable-like slots have over classical Perl objects built on hash keys or data provided by accessor methods is that the names are scoped within just the class body that defines them. Names cannot collide with those defined by subclasses. This is even checked by one of Object::Pad's own unit tests, which defines a base class and a subclass from it that both have a slot called $data:

class Base::Class {
    has $data;
    method data { $data }
}
 
class Derived::Class extends Base::Class {
    has $data;
    method data { $data }
}

It then has some tests to check that each of these methods behaves differently. In particular, this provides the guarantee that classes can freely add, delete, or rename their own slot variables without risking breaking other related classes. This leads to more robust class definitions.

<< First | < Prev | Next >

No comments:

Post a Comment