Classes and Objects

Many of Parrot's core classes -- such as Integer, String, or ResizablePMCArray -- are written in C, but you can also write your own classes in PIR. PIR doesn't have the shiny syntax of high-level object-oriented languages, but it provides the necessary features to construct well-behaved objects every bit as powerful as those of high-level object systems.

Parrot developers often use the word "PMCs" to refer to the objects defined in C classes and "objects" to refer to the objects defined in PIR. In truth, all PMCs are objects and all objects are PMCs, so the distinction is a community tradition with no official meaning.

Class Declaration

The newclass opcode defines a new class. It takes a single argument, the name of the class to define.

    $P0 = newclass 'Foo'

Just as with Parrot's core classes, the new opcode instantiates a new object of a named class.

  $P1 = new 'Foo'

In addition to a string name for the class, new can also instantiate an object from a class object or from a keyed namespace name.

  $P0 = newclass 'Foo'
  $P1 = new $P0

  $P2 = new ['Bar';'Baz']

Attributes

The addattribute opcode defines a named attribute -- or instance variable -- in the class:

  $P0 = newclass 'Foo'
  addattribute $P0, 'bar'

The setattribute opcode sets the value of a declared attribute. You must declare an attribute before you may set it. The value of an attribute is always a PMC, never an integer, number, or string.Though it can be an Integer, Number, or String PMC.

    $P6 = box 42
    setattribute $P1, 'bar', $P6

The getattribute opcode fetches the value of a named attribute. It takes an object and an attribute name as arguments and returns the attribute PMC:

    $P10 = getattribute $P1, 'bar'

Because PMCs are containers, you may modify an object's attribute by retrieving the attribute PMC and modifying its value. You don't need to call setattribute for the change to stick:

    $P10 = getattribute $P1, 'bar'
    $P10 = 5

Instantiation

With a created class, we can use the new opcode to instantiate an object of that class in the same way we can instantiate a new PMC.

    $P0 = newclass "Foo"
    $P1 = new $P0

Or, if we don't have the class object handy, we can do it by name too:

    $P1 = new "Foo"

PMCs have two VTABLE interface functions for dealing with instantiating a new object: init and init_pmc. The former is called when a new PMC is created, the later is called when a new PMC is created with an initialization argument.

    .namespace ["Foo"]
    .sub 'init' :vtable
        say "Creating a new Foo"
    .end

    .sub 'init_pmc' :vtable
        .param pmc args
        print "Creating a new Foo with argument "
        say args
    .end

    .namespace[]
    .sub 'main' :main
        $P1 = new ['Foo']       # init
        $P2 = new ['Foo'], $P1  # init_pmc
    .end

Methods

Methods in PIR are subroutines stored in the class object. Define a method with the .sub directive and the :method modifier:

  .sub half :method
    $P0 = getattribute self, 'bar'
    $P1 = $P0 / 2
    .return($P1)
  .end

This method returns the integer value of the bar attribute of the object divided by two. Notice that the code never declares the named variable self. Methods always make the invocant object -- the object on which the method was invoked -- available in a local variable called self.

The :method modifier adds the subroutine to the class object associated with the currently selected namespace, so every class definition file must contain a .namespace declaration. Class files for languages may also contain an .HLL declaration to associate the namespace with the appropriate high-level language:

  .HLL 'php'
  .namespace [ 'Foo' ]

Method calls in PIR use a period (.) to separate the object from the method name. The method name is either a literal string in quotes or a string variable. The method call looks up the method in the invocant object using the string name:

    $P0 = $P1.'half'()

    $S2 = 'double'
    $P0 = $P1.$S2()

You can also pass a method object to the method call instead of looking it up by string name:

    $P2 = get_global 'triple'
    $P0 = $P1.$P2()

Parrot always treats a PMC used in the method position as a method object, so you can't pass a String PMC as the method name.

Methods can have multiple arguments and multiple return values just like subroutines:

  ($P0, $S1) = $P2.'method'($I3, $P4)

The can opcode checks whether an object has a particular method. It returns 0 (false) or 1 (true):

    $I0 = can $P3, 'add'

Inheritance

The subclass opcode creates a new class that inherits methods and attributes from another class. It takes two arguments: the name of the parent class and the name of the new class:

    $P3 = subclass 'Foo', 'Bar'

subclass can also take a class object as the parent class instead of a class name:

    $P3 = subclass $P2, 'Bar'

The addparent opcode also adds a parent class to a subclass. This is especially useful for multiple inheritance, as the subclass opcode only accepts a single parent class:

  $P4 = newclass 'Baz'
  addparent $P3, $P4
  addparent $P3, $P5

To override an inherited method in the child class, define a method with the same name in the subclass. This example code overrides Bar's who_am_i method to return a more meaningful name:

  .namespace [ 'Bar' ]

  .sub 'who_am_i' :method
    .return( 'I am proud to be a Bar' )
  .end

Object creation for subclasses is the same as for ordinary classes:

    $P5 = new 'Bar'

Calls to inherited methods are just like calls to methods defined in the class:

    $P1.'increment'()

The isa opcode checks whether an object is an instance of or inherits from a particular class. It returns 0 (false) or 1 (true):

    $I0 = isa $P3, 'Foo'
    $I0 = isa $P3, 'Bar'

Overriding Vtable Functions

The Object PMC is a core PMC written in C that provides basic object-like behavior. Every object instantiated from a PIR class inherits a default set of vtable functions from Object, but you can override them with your own PIR subroutines.

The :vtable modifier marks a subroutine as a vtable override. As it does with methods, Parrot stores vtable overrides in the class associated with the currently selected namespace:

  .sub 'init' :vtable
    $P6 = new 'Integer'
    setattribute self, 'bar', $P6
    .return()
  .end

Subroutines acting as vtable overrides must either have the name of an actual vtable function or include the vtable function name in the :vtable modifier:

  .sub foozle :vtable('init')
    # ...
  .end

You must call methods on objects explicitly, but Parrot calls vtable functions implicitly in multiple contexts. For example, creating a new object with $P3 = new 'Foo' will call init with the new Foo object.

As an example of some of the common vtable overrides, the = operator (or set opcode) calls Foo's vtable function set_integer_native when its left-hand side is a Foo object and the argument is an integer literal or integer variable:

    $P3 = 30

The + operator (or add opcode) calls Foo's add vtable function when it adds two Foo objects:

    $P3 = new 'Foo'
    $P3 = 3
    $P4 = new 'Foo'
    $P4 = 1774

    $P5 = $P3 + $P4
    # or:
    add $P5, $P3, $P4

The inc opcode calls Foo's increment vtable function when it increments a Foo object:

    inc $P3

Parrot calls Foo's get_integer and get_string vtable functions to retrieve an integer or string value from a Foo object:

    $I10 = $P5  # get_integer
    say $P5     # get_string

Introspection

Classes defined in PIR using the newclass opcode are instances of the Class PMC. This PMC contains all the meta-information for the class, such as attribute definitions, methods, vtable overrides, and its inheritance hierarchy. The opcode inspect provides a way to peek behind the curtain of encapsulation to see what makes a class tick. When called with no arguments, inspect returns an associative array containing data on all characteristics of the class that it chooses to reveal:

  $P1 = inspect $P0
  $P2 = $P1['attributes']

When called with a string argument, inspect only returns the data for a specific characteristic of the class:

  $P0 = inspect $P1, 'parents'

Table 7-1 shows the introspection characteristics supported by inspect.