Copyright Notice

This text is copyright by CMP Media, LLC, and is used with their permission. Further distribution or use is not permitted.

This text has appeared in an edited form in SysAdmin/PerformanceComputing/UnixReview magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.

Please read all the information in the table of contents before using this article.

Unix Review Column 14 (May 1997)

In my previous column, I started examining Perl's Object-Oriented mechanisms. As an illustration, I created a ``class'' called ``Car'', and gave ``methods'' to access this class to allow passengers to enter, leave, and be accounted for. Although you can probably read this month's column without having the previous column in hand, I'd strongly recommend reading the previous one first. (If you have access to the Web, you can view the previous column online at http://www.stonehenge.com/merlyn/UnixReview/.)

So, continuing on with where I left off last time, let's take a couple of vehicles:

        my $myCar = new Car;
        my $myTruck = new Truck;

Now, this should create a new Car and a new Truck. Now, these two vehicles will be similar in many respects, but different in others. We'll represent this using ``inheritance'' from a common ``Vehicle'' class, like so:

        BEGIN {
          @Car::ISA = "Vehicle";
          @Truck::ISA = "Vehicle";
        }

The reason I'm wrapping this in a BEGIN block is that it needs to be executed before I call new, even though I have written these assignments later in the program. Typically, all @ISA settings should happen before the first ``real'' line of code gets executed.

But what do these do? As you'll see in a moment, there is no Car::new method. Instead, Perl will look for such a method in the Vehicle class. Similarly, there's no Truck::new method. Instead, all we have is the &Vehicle::new method:

        sub Vehicle::new {
          bless my $self = {}, shift;
          my %parms = @_;
          $self->{Passengers} = $parms{Passengers} || {};
          $self;
        }

Note that this is very similar to the Car::new method from my last column. The first line creates the empty anonymous hash, sticks it into $self, and blesses it into the right package. Note that the package comes from the first parameter, here grabbed with shift. The second line creates a named hash %parms from the remaining method arguments.

The third line here is unlike Car::new. I'm permitting the invoker to pass in a list of passengers that will be initially loaded into the car, like so:

        $myCar = new Car Passengers => qw(Fred Dino);

If such a list is not specified, the car starts out empty. The final line returns the newly created car.

So, this method can be used from both the Car and Truck classes to create Cars and Trucks. So far, however, the only difference between a Car and a Truck is its class, so we seem to have gone to a lot of trouble for nothing. Let's add a little more behavior to these vehicles, and then I'll start differentiating them.

First, let's add back in the enter, leave, and jump methods from the previous column:

        $myCar->enter(qw(Fred Barney));
        $myTruck->enter(qw(Wilma Betty));
        $myTruck->jump(To => $myCar, People => qw(Wilma));
        $myCar->leave(qw(Fred Barney Wilma));

In human terms, Fred and Barney get in the car, while Wilma and Betty hop in the truck. Suddenly, Wilma gets out of the truck and into the car, then persuades Fred and Barney to also get out of the car with her. (Betty is still alone in the truck.) Let's see how that would look as the implementation:

        sub Vehicle::enter {
          my $self = shift;
          for (@_) {
            $self->{Passengers}->{$_} = "seat";
          }
          $self;
        }

Here, the object is saved away into $self, then for each of the remaining method arguments, we add an entry into the Passengers hash with the key for the name and ``seat'' for the value (as in the previous column). Note that the method is defined in the Vehicle class, making it accessible directly to both Cars and Trucks. Similarly, we have the leave method:

        sub Vehicle::leave {
          my $self = shift;
          for (@_) {
            delete $self->{Passengers}->{$_};
          }
          $self;
        }

And here, the only difference between enter and <leave> is what happens to the Passengers hash. The jump method is a little scarier, but still pretty easy to read:

        sub Vehicle::jump {
          my $self = shift;
          my %parms = @_;
          my $dest_vehicle = $parms{To} ||
            new {ref $self};
          my $people = $parms{People} ||
            [keys %{$self->{Passengers}}];
          $people = [$people] unless ref $people;
          $self->leave(@$people);
          $dest_vehicle->enter(@$people);
          $dest_vehicle;
        }

First, the object is saved into $self, and the remaining method arguments form the hash %parms. A target vehicle has to be selected. If there's a hash entry for To, that's preferred, but if not, a new vehicle is created.

But what type should the vehicle be? Well, a bit of arbitrary decision making combined with years of programming experience led me to conclude that the new unspecified vehicle should be of the same size and shape as the vehicle being exited. We can get this by calling ref $self, which is then passed in as the ``object'' on a new call. (Wouldn't it be nice to be able to create a duplicate object this easily in real life? But I digress...)

Next comes the designated jumpers. That's either gonna be the People parameter, or everyone currently on board. Note that $people here is expected to be a listref, but if someone passes in a scalar name, we'll have trouble later, so the line that begins $people = turns a simple name into a one-element anon-list if necessary.

Once the source vehicle ($self), the people (@$people), and the destination vehicle ($dest_vehicle) have been determined, we can start moving people around. First, the people are asked to leave the source vehicle, and then they are asked to join the destination vehicle. Finally, the new vehicle is returned (which could be the same as the one passed in as To, or it might be a brand new Car or Truck).

Well, this will certainly implement everything necessary to handle that sequence of events above. To check our work, I'll need some way to access the current passenger list:

        sub Vehicle::passengers {
          my $self = shift;
          sort keys %{self->{Passengers}};
        }

Why don't I just let the users of the Car and Truck class know that $self->{Passengers} is a hash with the keys being the current passengers? Well, I could choose to do that, if I'm lazy, or performance is very critical. However, I would then be locked into that interface forever, even if I found out later that it would be easier to implement the passengers as a list instead of a hash. Hence, it's better to provide access methods such as this one, and encourage users not to ``peer behind the scenes''.

These vehicle classes have woefully little error checking. Let's add a bit of that, and illustrate why Car and Truck are different classes at the same time:

        sub Vehicle::enter {
          my $self = shift;
          if (@_ + keys %{$self->{Passengers}} > $self->seats) {
            die "Cannot add @_ to $self";
          }
          for (@_) {
            $self->{Passengers}->{$_} = "seat";
          }
          $self;
        }

OK, let's see what's going on here... I'm adding the length of the argument list (in @_) to the number of keys, to see if it's greater than some number, returned by calling the seats method. If it's greater, then we've got too many people trying to sit in the car, and death occurs.

But what is this seats method? Well, I haven't defined it yet, but it'll look like this:

        sub Vehicle::seats { 2 }

Hmm. That wasn't hard. Now trucks and cars all have 2 seats, along with anything else that inherits from Vehicle. But wait -- cars can seat 4 people! So let's add that fact:

        sub Car::seats { 4 }

When $myCar->seats gets called, it returns 4 (because we find seats directly in Car). But when $myTruck->seats gets called, there is no Truck::seats method, so Perl searches the inheritance path to find the default seat-count in Vehicle! For grins, we could define a few more types:

        BEGIN {
          @Motorcycle::ISA = "Vehicle";
          @Van::ISA = "Vehicle";
          @Unicycle::ISA = "Vehicle";
        }
        sub Van::seats { 8 }
        sub Unicycle::seats { 1 }

And note that I didn't define a seats for a Motorcycle, because the default (2) was already correct.

What you're seeing here is an example of inheritance and method overloading. Inheritance, in that seats for 2-seaters comes directly from Vehicle. Overloading, in that seats for the others overshadows the definition in Vehicle.

I hope you've enjoyed this brief stroll into Objectland, and can see some possibilities. Enjoy!


Randal L. Schwartz is a renowned expert on the Perl programming language (the lifeblood of the Internet), having contributed to a dozen top-selling books on the subject, and over 200 magazine articles. Schwartz runs a Perl training and consulting company (Stonehenge Consulting Services, Inc of Portland, Oregon), and is a highly sought-after speaker for his masterful stage combination of technical skill, comedic timing, and crowd rapport. And he's a pretty good Karaoke singer, winning contests regularly.

Schwartz can be reached for comment at merlyn@stonehenge.com or +1 503 777-0095, and welcomes questions on Perl and other related topics.