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
.