PDD 15: Objects and Classes
Abstract
This PDD describes the semantics of Parrot's object and class systems.
Version
$Revision$
Definitions
Object
An object is a value that incorporates both data and behavior related to that data.
Class
A class defines a pattern of characteristics and behaviors from which objects are constructed.
Attribute
An attribute is a slot in an object that contains a value, generally a PMC. Attributes are referenced by class name/attribute name pairs.
Attributes are set on a class-wide basis, and all the objects of a class will have the same set of attributes. Most OO languages don't allow attribute changes to existing classes, but Parrot's base attribute system does allow it. In order to safely support advanced dynamic features in HLLs, attributes are not accessible via fixed attribute offsets, but only via named lookup.
Method
A method is a piece of code that you invoke by name through an object. Methods implement the behaviour of an object.
Parent class
Also called the super-class. The parent class is, in an inheritance situation, the class being derived from. If A derives from B, B is the parent class of A.
Child class
Also called the sub-class. The child class is, in an inheritance situation, the class doing the deriving. If A derives from B, A is the child class.
Role
A role adds attributes and methods into a class without inheritance.
The composed class retains a list of roles applied to it (so they can be checked with does
),
but otherwise maintains no distinction between composed attributes and methods and those defined in the class.
Delegate
An object that is transparently (to the user) embedded in another object. Delegate objects are used in those cases where we can't inherit from a class because the class is from a different object universe.
Property
A property is a role that only adds attributes and accessors.
Properties are generally assigned at runtime, and a particular property may or may not exist on a PMC at any particular time. Properties are not restricted to objects as such--any PMC may have a property attached to it.
Interface
An interface is a role that only adds methods.
Description
- - The object and class system provides the flexibility to implement a core set of dynamic languages (Perl 6, Ruby, Python, etc). Other class systems may be implemented later to support other languages.
- - Classes may have an associated namespace. (Anonymous classes are not associated with a namespace.)
- - Classes may have one or more immediate parent classes
- - Classes may have a catalog of attribute names.
- - Classes may have a list of roles they implement
- - Classes can instantiate an object of their class
- - Classes can add and remove parent classes
- - Classes can add and remove attributes
- - Classes can add (but not remove) roles
- - Classes are instances of a meta-class and have their own sets of class methods and class attributes
- - Objects may have an array of attributes. Attribute values may be PMCs or a low-level type.
- - Objects have an associated class.
- - Objects may have a custom vtable or use a class-wide vtable.
- - Objects can call a method
- - Objects can retrieve a method PMC for a method (for deferred method calls)
- - Objects can fetch their class
- - Objects can get an attribute by name
- - Objects can set an attribute by name
- - Objects can be subclassed (note that objects may not necessarily be able to have their classes changed arbitrarily, but making a subclass and moving the object to it is allowable)
- - High-level objects can subclass low-level PMCs
Implementation
There are four pieces to the object implementation. There are the PMCs for the classes, roles, and objects, the opcodes the engine uses to do objecty things, the specific vtable functions used to perform those objecty things, and the supporting code provided by the interpreter engine to do the heavy lifting.
Parrot, in general, doesn't restrict operations on objects and classes. If a language has restrictions on what can be done with them, the language is responsible for making sure that disallowed things do not happen. For example, Parrot permits multiple inheritance, and will not stop code that adds a new parent to an existing class. If a language doesn't allow for multiple inheritance it must not emit code which would add multiple parents to a class. (Parrot may, at some point, allow imposition of runtime restrictions on a class, but currently it doesn't.)
Class PMC API
There are two PMC classes,
Class
and Object
.
Class PMCs hold all the class-specific information.
Instantiating a new OO class creates a new Class PMC,
and enters the new OO class into Parrot's PMC class table,
at which point it is indistinguishable from any other PMC class.
It's important to note that 'standard' classes are Class PMC instances, or instances of a subclass of the Class PMC, and 'standard' objects are Object PMCs. It isn't necessary to create a brand new low-level PMC class for each OO class, and they all share the Class or Object vtable, respectively.
An instance of the Class PMC has eleven internal attributes, which are:
- 0 The class name
- 1 A link to the class's associated namespace
- 2 A "have I been instantiated since I was last modified" flag
- 3 An array PMC of the immediate parent classes
- 4 A cached array of all parent PMCs, in search order (this is an optional optimization, and can be calculated from the class's rules of inheritance, the list of immediate parent classes, and the parent classes' rules of inheritance)
- 5 An array PMC of the composed roles (these are Role PMCs, not string names of roles).
- 6 A hash PMC of the methods defined in the class or composed into the class
- 7 A hash PMC of the overloaded PMC vtable entries for the class.
- 8 The class attribute metadata hash. Keys are the attribute names and the values are a hash of attribute characteristics, including name, type, the class they're associated with and any flags (for example, private). Note that this only stores metadata for the attributes defined in this class, and not for attributes inherited from its parents.
- 9 The full attribute lookup table. This associates attribute names with an index into the object's attribute storage (an array). It includes all attributes defined in the current class and every other class that it inherits from either directly or indirectly. The table is keyed on the name of the class where the attribute is defined, along with the attribute name. The value is an index into the per-object attribute store.
- 10 The attribute cache. While the attribute lookup table defines every attribute, whether it is visible or not with the current method resolution order (MRO), the cache maps the names of the visible attributes directly to an index in the per- object attribute store. That saves a more costly lookup in the full attribute lookup table.
The attribute catalog holds only the attributes defined in a particular class. When instantiating an object, the object data store is created as a ResizablePMCArray, so doesn't need any specific details of the class's attribute structure. As attributes are set in the object (based on the index in the lookup table), the Array expands to accommodate the attribute indexes that are actually used. In the common case, a relatively small set near the lower index range is all that will be used.
When setting the attribute cache it is necessary to scan all parent classes as well as the instantiated class for attributes defined there. The inheritance rules (MRO) for a particular HLL will determine which child class attributes override which parent class attributes. The cache is only set on individual accesses to a particular attribute.
(If a parent class changes its set of attributes, should that change appear in later instantiations of objects from child classes? If so, all of these classes would need to be re-constructed as a result of the change; note that any already instantiated objects would refer to the old class. NOTE: flag old classes with an "updated" status, to notify objects of the old class that they should rebless themselves into the new class next time they access the old class?)
Class PMCs also have the "I am a class" flag set on them.
Classes, Namespaces, and the Class Registry
Extending an existing class that has been instantiated creates a new class object that replaces the old class object in the Namespace. However, the old class object must be kept, as the old objects still point to it and do their method resolution and attribute lookup through that class object.
If a class hasn't been instantiated, adding a method or attribute only modifies the existing class object instead of creating a new class object. Extending a class that has been instantiated only causes the creation of a new class object the first time it's extended. After that, methods and attributes added to it will only modify the existing class object until it is instantiated again.
The Namespace always points to the most current incarnation of the class. All the class objects that belong to a particular namespace store a pointer to that Namespace object. They keep that pointer even if the Namespace object no longer stores a pointer to them.
Since any given class name may have multiple corresponding class objects, the class registry has a much diminished role in this implementation. Its only responsibility is maintaining a mapping of unique IDs to class objects throughout the system. It can't be used for looking up classes by name, because it's possible to have multiple classes with the same name in the same namespace. The class registry may need to have names removed (since it doesn't care about names anymore). Low-level PMC types will also need entries in the namespace hierarchy. We may eventually be able to eliminate the registry of class IDs altogether.
A class can be garbage collected when it has no instantiated objects and no Namespace object referencing it (to mark it as live). When a class is garbage collected, it should remove itself from the registry.
Class Vtable Entries
To make this work all Classes need the following vtable entries.
- instantiate() Instantiate a new object from the class. Set the instantiated flag on the class.
- clone() Create an (anonymous) clone of the class. Unset the instantiated flag on the new class.
- name(string *) Returns a simple string name for the class.
- add_method(string *, method *) Add a method to the class.
- add_vtable_override(string *, vtable_sub *) Add a vtable override to the class.
- add_attribute(string *, key *) Add an attribute to the class.
- add_parent(class *) Add a parent to the class.
- add_role(role *) Add a role to the class.
- find_method(string *) Returns the PMC for the named method. If no method of this name exists, nor can be constructed, returns a Null PMC.Note that for languages which support default fallback methods, such as Perl 5's AUTOLOAD, this would be the place to return it if a normal lookup fails.Since the method list and vtable override list are stored in the class PMC, method finding is a lookup on the class object and not a lookup in the namespace. (This could be handled automatically whenever a class is associated with a namespace.) Just adding a sub to a namespace will not automatically make it a method of the class, you have to call add_method too.
- isa(pmc *) Returns true or false if the class object, namespace object, key, or string name PMC passed in as a parameter is a class in the inheritance hierarchy of the object.
- isa_str(string *) Returns true or false if the string passed in as a parameter is a class in the inheritance hierarchy of the object.
- can(string *) Returns true or false if the class can perform the requested method. (Including with an AUTOLOAD)A class object only reports on class methods, it does not report on instance methods.
- does(class *) Returns true or false to note whether the class in question implements the interface passed in.A class object only reports on interfaces of the class (i.e. roles composed into the metaclass), while an instance object only reports on interfaces of the instance (i.e. roles composed into the class).
- inspect() Return a data structure of all information relevant to introspection on the class.
- inspect_str(string *) Return a PMC Hash, Array, String, Integer, or Number value with introspection information corresponding to the requested string name. This may be overridden to report information about the internals of a class that aren't actually true (useful for mocking). It can also be used for straight introspection capabilities even when a particular class is using keyed access to act like a hash or array or attribute access to act as an object.
- remove_attribute(string *) Remove an attribute from the class.
- remove_method(string *) Remove a method from the class.
- remove_parent(string *) Remove a parent from the class.
- remove_role(string *) Remove a role from the class.
Currently Parrot only supports mutating a class' metainformation for Class classes. This is a restriction which will be lifted at some point soon.
Class Methods
These methods are just syntactic sugar for the vtable functions. They are not included in the Class PMC by default, but added to Class as a role.
- name
- namespace
- new
- add_attribute
- attributes
- add_method
- methods
- add_parent
- parents
- roles
- add_role
- subclass
- isa
- can
- does
- inspect
$P1 = $P2.name( $S3 )The accessor for the name attribute. With no argument, it simply returns the current value for name. When passed an argument, it sets the name of the class, and also sets the association with a namespace. With no argument it only returns the current value of the name attribute.
$P1 = $P2.namespace()Retrieve the namespace object associated with the class.
$P1 = $P2.new( 'myattrib' => "Foo" )Create a new instance object from the class object. It takes an optional, slurpy, named list of attributes and values to initialize the object. Passing attribute names that weren't declared in the class is an error.
$P1.add_attribute($S2) $P1.add_attribute($S2, $S3) $P1.add_attribute($S2, $P3)Adds a single attribute to the class. It takes a simple string name and, optionally, a simple string value or key specifying a type name. (A type name just checks
does
, and doesn't necessarily correspond to a class or role namespace.)If the class has already been instantiated, adding a new attribute triggers the creation of a new class, replacing the old class. See "Classes, Namespaces, and the Class Registry".
$P1 = $P2.attributes()An accessor for the attributes of the class. It returns the a Hash of all attributes, with a key of the attribute name and a value of a Hash containing the attribute's metadata. The accessor is read-only.
$P1.add_method($S2, $P3) $P1.add_method($S2, $P3, 'vtable' => 1)Adds a method to the class. It takes a simple string name and a method PMC. If the method already exists (and isn't a Multi) it will replace the method with the new method and throw a warning.It also takes named parameters to flag whether the method is a vtable override, and whether it is anonymous (no named entry as a method, only as a vtable).
$P1 = $P2.methods()An accessor for the methods of the class. It returns a Hash of all methods, with a key of the method name and a value of an invokable PMC. Note that the methods list includes any methods that were composed into the class from roles.
$P1.add_parent($P3)Adds a single parent to the class. It takes an instance of the Class PMC.
$P1 = $P2.parents()An accessor for the parents of the class. It returns an Array of all parents. The accessor is read-only.
$P1 = $P2.roles()An accessor for the roles of the class. It returns an Array of all roles. The accessor is read-only.
$P1.add_role($P2, [named])Adds a single role to the class. It takes an instance of the Role PMC as a required positional parameter, and the optional named parameters
exclude
and alias
; see "Role Conflict Resolution" for more details.
$P1 = $P2.subclass($S3)Create a subclass of $P2 with name $S3 and return it in $P1.
$I1 = $P2.isa($S3)Returns true if the class name passed in as a parameter is in the inheritance hierarchy of the class (this is not the same as the inheritance hierarchy of objects instantiated from the class), false otherwise.
$I1 = $P2.can($S3)Returns true if the class object can perform the requested method, false otherwise.A class object only reports on class methods, not on instance methods.
$I1 = $P2.does($S3)Returns true if the object in question implements the role, class, type, or behavior passed in, false otherwise.A class object only reports on interfaces of the class (i.e. roles composed into the metaclass), while an instance object only reports on interfaces of the instance (i.e. roles composed into the class).
$P1 = $P2.inspect() $P1 = $P2.inspect($S3)Return introspection information for the class.
Object PMC API
Object
PMCs are the actual objects, and hold all the per-object instance data.
The Object PMC is an array of meta-information and attributes. The elements of this array are:
- 0 The class PMC
- 1 The object attribute store. This is simply an array of PMCs that provide the values for the attributes. It may be a resizable PMC array to provide lazy growth rather than allocating all needed memory for all attributes. We find the indexes into this array from the attribute cache or lookup table in the class.
A list of the object's attributes is accessible from the class. The attribute cache is the most straightforward way to retrieve a complete list of attributes visible to the object, but the first time you introspect for a complete list the class may have to calculate the list by traversing the inheritance hierarchy.
Object PMCs have the "I am an object" flag set on them.
Object PMCs have no methods aside from those defined in their associated class. They do have vtable functions providing access to certain low-level information about the object, method call functionality, etc. See the sections below on Objects and Vtables.
In addition to a value type, objects can have a container type. The container type can't be stored in the object itself, because a single object may live within multiple containers. So, the container type (when it exists) is stored in the LexPad or Namespace entry for a particular variable.
In a static language like C#.Net:
B isa A A o1 = new B(); B o2 = new B(); o1.x; # retrieves A's attribute o2.x; # retrieves B's attribute o1.foo(); # calls B's method o2.foo(); # calls B's method
Object Vtable Entries
All Objects need the following vtable entries.
- find_method(string *) Returns the PMC for the named method. If no method of this name exists, nor can be constructed, returns a Null PMC. This only passes the method search on to the object's class.
- isa(class *) Returns true or false if the class passed in as a parameter is in the inheritance hierarchy of the object.
- can(string *) Returns true or false if the object can perform the requested method. (Including with an AUTOLOAD)An instance object only reports on instance methods, not on class methods.
- does(class *) Returns true or false to note whether the object in question implements the interface passed in.An instance object only reports on interfaces of the instance (i.e. roles composed into the class).
- get_attr(STRING*) Returns the attribute with the string name for the object.
- get_attr_keyed(PMC*, STRING*) Returns the attribute with the string name for the object. Lookup is on the attribute store of a particular parent of the object's class, identified by a classname, namespace, or key PMC.
- set_attr(STRING*, PMC*) Set the attribute with the string name for the object.
- set_attr_keyed(PMC*, STRING*, PMC*) Set the attribute with the string name for the object. The value is set in the attribute store of a particular parent of the object's class, identified by a classname, namespace, or key PMC.
- get_class Returns the class PMC for the object.
- clone Create a clone of the object.
- inspect() Return a data structure of all information relevant to introspection on the object.
- inspect_str(string *) Return a PMC Hash, Array, String, Integer, or Number value of introspection information corresponding to the requested string name (such as 'parents'). This may be overridden to report information about the internals of an object that aren't actually true (useful for mocking). It can also be used for straight introspection capabilities even when a particular object is using keyed access (to act like a hash or array) or attribute access.
Role PMC API
An instance of the Role PMC has five attributes, which are:
- 0 The role name
- 1 A link to the role's associated namespace
- 2 An array PMC of composed roles
- 3 An array PMC of the methods defined in the role or composed into the role
- 4 The role attribute hash. Keys are the attribute names and the values are a hash of attribute characteristics, including name, type, and the role they're associated with.
Role Vtable Entries
All Roles need the following vtable entries.
- add_method(string *, method *) Add a method to the role.
- add_vtable_override(string *, vtable_sub *) Add a vtable override to the role.
- add_attribute(string *, key *) Add an attribute to the role.
- add_role(role *) Add a role to the role.
- find_method(string *) Returns the PMC for the named method. If no method of this name exists, nor can be constructed, returns a Null PMC.
- can(string *) Returns true or false if the role can perform the requested method. (Including with an AUTOLOAD)
- does(class *) Returns true or false to note whether the role in question implements the interface passed in.A role object only reports on interfaces of the role (i.e. roles composed into the metarole), it doesn't report on which interfaces will be added to an object that composes the role.
- clone Create an (anonymous) clone of the role.
- inspect() Return a data structure of all information relevant to introspection on the role.
- inspect_str(string *) Return a PMC Hash, Array, String, Integer, or Number value of introspection information corresponding to the requested string name (such as 'parents').
- remove_attribute(string *) Remove an attribute from the role.
- remove_method(string *) Remove a method from the role.
- remove_role(string *) Remove a role from the role.
Role Methods
These methods are just syntactic sugar for the vtable functions. They are not included in the Role PMC by default, but added to Role as a role.
- name
- namespace
- attributes
- add_attribute
- add_role
- roles
- add_method
- methods
- inspect
$P1 = $P2.name( $S3 )The accessor for the name attribute. With no argument, it simply returns the current value for name. When passed an argument, it sets the name of the role, and also sets the association with a namespace.
$P1 = $P2.namespace()Retrieve the namespace object associated with the role.
$P1 = $P2.attributes()An accessor for the attributes of the role. It returns the Hash of all attributes, with a key of the attribute name, and a value of the attribute's metadata (a Hash). The accessor is read-only.
$P1.add_attribute($S2) $P1.add_attribute($S2, $S3) $P1.add_attribute($S2, $P3)Adds a single attribute to the role. It takes a simple string name, and optionally, a simple string value or key specifying a type name. (A type name just checks
does
, and doesn't necessarily correspond to a class or role namespace.)
$P1.add_role($P2, [named])Adds a single role to the role. It takes an instance of the Role PMC as a required positional parameter, and the optional named parameters
exclude
and alias
; see "Role Conflict Resolution" for more details.
$P1 = $P2.roles()An accessor for the roles composed into the role. It returns an Array of all roles as PMC objects. If any roles that were composed into this one were themselves made up of a composition of other roles, the roles they were made up of will also be included in the value returned by this accessor. However, no role will be mentioned more than once. The accessor is read-only.
$P1.add_method($S2, $P3) $P1.add_method($S2, $P3, 'vtable' => 1)Adds a method to the role. It takes a simple string name and a method PMC. If the method already exists (and isn't a Multi) it will replace the method with the new method and throw a warning.It also takes slurpy named parameters to flag whether the method is a vtable override, and whether its anonymous (no named entry as a method, only as a vtable).
$P1 = $P2.methods()An accessor for the methods of the role. It returns a Hash of all methods, with a key of the method name and a value of an invokable PMC. The list will include methods added through composing other roles into this role. The accessor is read-only.
$P1 = $P2.inspect() $P1 = $P2.inspect($S3)Return introspection information for the role.
Role Conflict Resolution
When a role is added to a class, we try to compose it right away, and throw an exception on any conflicts that are detected. A conflict occurs if two roles try to supply a method of the same name (but see the note on multi-methods below). High level languages will provide varying facilities to deal with this, and Parrot provides the primitives to implement them.
When declaring a composed class, you can optionally supply an array of method names that will be defined by the class to resolve a conflict in its roles. This is done using the named parameter resolve
. This feature supports composition conflict resolution in languages such as Perl 6.
When adding a role to a class, you can optionally supply an array of method names from the role to exclude from the composition process. This is done using the named parameter exclude
. It is not an error to list a method name in this array that the role does not have. This makes it possible to implement languages that provide for explicit exclusions on a role-by-role basis.
When adding a role to a class, you can optionally specify that specific methods are to be aliased to different names within the class. This is done with the optional alias
named parameter. The parameter takes hash of strings, where the key is a method name in the role, and the value is the name it will have in to the class. (This is also sometimes used for conflict resolution.)
If you alias
a method, it won't automatically exclude
the original name from the role. You can also explicitly exclude
the method name, if you want a proper renaming of the method. A resolve
at the class level will automatically exclude
all methods of that name from any role composed into the class. You can alias
the method if you want to call it from the composed class. (You might use this if you want the resolving method to be able to call either of the conflicting methods from two composed roles.)
If a method in a role is a MultiSub PMC and there is either no method of that name yet OR what is in the method slot with that name is also a MultiSub PMC, there will be no error. Instead, the multi-methods from the role will be added to the multi-methods of the MultiSub PMC already in the class. Any attempt to combine a multi with a non-multi will result in an error.
Opcodes
The following ops are provided to deal with objects. Please note that method calls are governed by Parrot's calling conventions, and as such objects, method PMCs, return continuations, and parameters must be in the right places, though some ops will put parameters where they need to go.
- getattribute
- setattribute
- callmethod
- callmethodcc
- callmethodsupercc [hypothetical, 2.0 or later]
- callmethodnextcc [hypothetical, 2.0 or later]
- newclass
- subclass
- get_class
- typeof
- new
- addparent
- removeparent
- addattribute
- removeattribute
- addrole
- inspect
$P1 = getattribute $P2, $S3 $P1 = getattribute $P2, $P3, $S4Get the attribute with the fully qualified name $S3 from object $P2 and put it in $P1. To get an attribute for a parent class that has the same name as an attribute in the child class, pass an optional class object or namespace key $P3 for the parent class.If the attribute doesn't exist, it will throw an exception. If the attribute exists, but the value hasn't been set, it will return a null PMC.
setattribute $P1, $S2, $P3 setattribute $P1, $P2, $S3, $P4Set the attribute of object $P1 with the attribute name $S2 to $P3. To set an attribute for a parent class that has the same name as an attribute in the child class, pass an optional class object or namespace key $P2 for the parent class.If the attribute doesn't exist, it will throw an exception.
callmethod $P1, $S1, $P2Call the method specified in the attribute $S1 using $P1 as the invocant and using the continuation passed in $P2. If you need to create a new continuation use
callmethodcc
.
callmethod $P1, $P2, $P3Call the method specified in the Sub object $P2 using $P1 as the invocant and using the continuation passed in $P3. If you need to create a new continuation use
callmethodcc
.
callmethodcc $P1, $S1 callmethodcc $P1, $P2Call the method specified in the attribute $S1, or in the Sub object $P2, using $P1 as the invocant for method lookup and generate a new return continuation.Throws a Method_Not_Found_Exception for a non-existent method.
callmethodsupercc $P1, $S1 callmethodsupercc $P1, $P2Call the method specified in the attribute $S1, or in the Sub object $P2, using $P1 as the invocant for method lookup and generate a new return continuation. This is a variant of
callmethodcc
that skips over the current class when searching for the method, and only looks in the parent classes. PIR may provide some syntactical sugar for this.
callmethodnextcc $P1, $S1 callmethodnextcc $P1, $P2Call the method specified in the attribute $S1, or in the Sub object $P2, using $P1 as the invocant for method lookup and generate a new return continuation. A variant of
callmethodcc
that picks up an existing find_method
search where it left off for the current call. {{ Note: this depends on find_method being resumable, and on the context of a particular method including a pointer to the find_method call that found it. Neither may be feasible. }} PIR may provide some syntactic sugar for this.
$P1 = newclass $S2 $P1 = newclass $S2, $P3Create a new base class named $S2, and put the PMC for it in $P1. You may optionally pass a hash of initialization parameters for the class in $P3.
$P1 = subclass $S2 $P1 = subclass $P2 $P1 = subclass $S2, $S3 $P1 = subclass $P2, $S3 $P1 = subclass $S2, $P3 $P1 = subclass $P2, $P3Create a new class, named $S3, which has $P2 as its immediate parent. $P2 may be either another high-level class based on the Class PMC, or it may be a low-level PMC such as
Integer
or ResizablePMCArray
.
$P1 = get_class $S2 $P1 = get_class $P2Retrieve a class object for the class identified by the string name in $S2, or by the PMC key or namespace object in $P2.A string name looks for the class in a namespace with that name nested in the currently selected namespace. Passing in a namespace object looks for the class in that namespace object. A key looks for the class in the namespace identified by the multilevel key relative to the currently selected HLL.If the class doesn't exist, it returns a null PMC.
$S1 = typeof $P2 $P1 = typeof $P2Lookup the type of the instance object in $P2. Return the string name if the destination is a string register or variable. If the destination is a PMC register or variable, return the class object for an instance of a high-level class, or the class proxy object for an instance of a low-level PMC.
$P1 = new $S2 $P1 = new $S2, $P3 $P1 = new $P2 $P1 = new $P2, $P3Create a new object from the class named by $S2 or $P2 (a string PMC, namespace key, or class object), and put the PMC for it in $P1. You may optionally pass a hash of initialization parameters for the class in $P3.
addparent $P1, $P2Add class $P2 to the end of the list of immediate parents of class $P1. Adds any attributes of $P2 (and its parent classes) that aren't already in $P1.
removeparent $P1, $P2Remove class $P2 from the parent list of $P1. All parent classes of $P2 which aren't parent classes of what remains of $P1's parent list are removed, as are their attributes.
addattribute $P1, $S2 addattribute $P1, $S2, $S3 addattribute $P1, $S2, $P3Add attribute $S2 to class or role $P1. This will add the attribute slot to all objects of class $P1, classes that inherit from class $P1, or classes that compose the role $P1, with a default value of
Null
. It optionally takes a simple string value or key specifying a type of the attribute.
removeattribute $P1, $S2Remove the attribute $S2 from class or role $P1. This will not remove the attribute from any objects that have already been instantiated, but it will be absent from all future objects of class $P1, of classes that inherit from class $P1, or of classes that compose the role $P1.
addrole $P1, $P2Add role $P2 to the end of the list of roles of class or role $P1. Adds any methods and attributes of $P2 that aren't already in $P1.
$P1 = inspect $P2 $P1 = inspect $P2, $S3Return introspection information for the PMC. Without the optional string argument, return a data structure of all information relevant to introspection. With the optional string argument, return a PMC Hash, Array, String, Integer, or Number value with introspection information corresponding to the requested string name.
PIR Class Definitions
PIR provides some syntactic sugar for declaring classes.
- :method
- :vtable
.sub custom_method :method # ... .endFlags the code entity as a method.
.sub get_integer :vtable # ... .endFlags the code entity as a vtable override.
:method and :vtable can be combined to indicate that a particular code entity is callable both as a method and as a vtable override.
If the class object has not yet been created at the point when the PIR subs are compiled, the methods and vtable overrides are temporarily stored in the associated namespace.
Vtable Overriding
Classes may override the vtable functions, allowing objects of a class to behave like a primitive PMC. To use these properly at a low-level requires a good working knowledge of the way Parrot works--generally for higher-level languages the language compiler or runtime will provide easier-to-use wrappers. These methods are all prototyped, and take a single fixed argument list, and return at most a single value.
To override a vtable function, either add the :vtable pragma to the declaration of the method, or pass a named parameter "vtable" into the add_method
method on a class or role.
What The Bytecode Sees
The bytecode is isolated from most of the internal details of the implementation. This allows both for flexibility in the implementation and forward compatibility, generally good things. It also allows for multiple concurrent interoperable object systems. The major thrust is for transparent use of objects, though most class activity (including creation of subclasses and modifications of existing classes) should be transparent as well.
Examples
The following examples all assume we're working with basic Object objects and Class classes.
Creating a new class
To create a new class Foo
which has no parent classes:
newclass $P0, "Foo"
Creating a new class with multiple parents
To create a class Foo
with the parents A
and B
, the code would be:
get_class $P0, "A" get_class $P1, "B" subclass $P2, $P0, "Foo" addparent $P2, $P1
Creating a new class with attributes
Adding the attributes a
and b
to the new class Foo
:
$P0 = newclass "Foo" addattribute $P0, "a" addattribute $P0, "b"
Instantiating an object
Assuming we want an object of class Foo
:
.local pmc FooClass .local pmc MyObject FooClass = get_class "Foo" MyObject = FooClass.new()
Calling a method on an object
Calling the method Xyzzy
on an object, assuming the PDD03 calling conventions are respected:
callmethod "Xyzzy" set S0, "Xyzzy" callmethod
Or, if a return continuation needs constructing:
callmethodcc "Xyzzy" set S0, "Xyzzy" callmethodcc
Accessing attributes from within a class
With named access:
getattribute $P1, $P0, "Foo\x0b"
Explanations
To get a new class, you can do a newclass
, which creates a new class with no parents besides Parrot's default super-ish parent class.
To get a new child class, you have two potential options:
Both ways work. It is, however, more efficient to use the first method, and just subclass the immediate parent class of your new class.
When adding in extra parents in a multiple-inheritance scenario, subclass the first class in the immediate parent list then use the addparent
op to add in the rest of the immediate parents.
Language Notes
Notes on some of the OO-related needs of various languages.
PMCs
Ruby: Just like Smalltalk, everything is an object. I'm hoping to be able to implement core Ruby classes (String, Array, Hash, Module, etc) something like this.
ParrotClass | RubyClass String | | \ / RubyString
Objectspace
Ruby: Objectspace in Ruby allows the programmer to iterate through every live object in the system. There is some debate about how to make this play nice with different garbage collection schemes.
Classes
A class is a collection of methods and attributes. It would be desirable, for those classes whose definition is fully known at compile time, to have a convenient way to have the class along with its attributes and methods stored into a PBC file rather than created at runtime. However, creation of new classes at runtime will be needed too.
Meta-classes
Ruby: Ruby has meta-classes. It would be nice if classes were objects in Parrot's OO model.
Attributes
Attributes are instance data associated with a class (or role, however those are supported). They may not always be of a type specified by a PMC, though boxing/unboxing is of course an option.
Perl 6: All attributes are opaque (not externally visible, even to any subclasses).
.Net: Attributes may be private (not externally visible), public (always externally visible), protected (only visible to subclasses) and internal (only visible inside the current assembly - the closest correspondence in Parrot is perhaps only visible inside the same PBC file). Additionally, it is allowable for a subclass to introduce an attribute of the same name as the a parent class has, and they both exist depending on what type an instance of the class is currently viewed as being (read: there is a difference between the type of the reference and the type of the value).
Ruby: Attributes can be dynamically added and removed at runtime.
Methods
Perl 6: Methods may be public (anyone can invoke them) or private (only invokable by the class they are defined in). Additionally, submethods are methods that do not get inherited.
.Net: Like attributes, methods may be public, private, protected or internal.
Ruby: has a method_missing that gets called when method resolution fails to find a method. Methods can be dynamically added and removed at runtime.
Constructors
A constructor is run when an object is instantiated.
.Net: There may be many constructors for an object (provided they all have different signatures), and the correct one is called based upon the passed parameters.
Inheritance
Perl 6: Multiple inheritance.
.Net: Single inheritance.
Ruby: Single inheritance but support for mixins of Ruby modules.
Interfaces
An interface specifies a set of methods that must be implemented by a class that inherits (or implements) the interface, but does not provide any form of implementation for them.
.Net: Interfaces are pretty much what was just describe above. XXX Need to check behavior of you implement two interfaces with methods of the same name.
Roles
A role consists of a set of methods and attributes. It cannot be instantiated on its own, but must be composed into a class. When this happens its methods and attributes become of that classes methods and attributes. This may happen at compile time or runtime, however when a role is composed into a class at runtime then what really happens is that a new anonymous class is created with the role composed into it and then the namespace entry for the existing class is updated to refer to the new one. Note that this means classes must be garbage collectable, with all those referred to by a namespace or with objects of that class existing being marked live.
Perl 6: Roles pretty much are a Perl 6 thing, so the definition above contains all that is needed. An open question is whether Parrot worry about collision detection? For compile time composition that's easy to punt to the compiler; for runtime composition, that's not so easy though.
Introspection (aka Reflection)
Perl 6: Reflection provides access to a list of methods that a class has, its parent classes and the roles it does, as well as the name of the class and its memory address. For methods, their name, signature, return type and whether the method is declared multi are available.
.Net: Reflection provides access to a list of attributes and methods as well as the name of the class and its parent. The types of attributes and signatures of methods are also available.
Inner Classes
An inner class is essentially a class defined within a class. Therefore it has access to things private to its outer class.
Perl 6: Inner classes are allowed, and may also be private.
.Net: Inner classes are allowed and may be private, public, protected or internal.
Delegation
Delegation is where a method call is "forwarded" to another class. Parrot may provide support for simple cases of it directly, or could just provide a "no method matched" fallback method that the compiler fills out to implement the delegation.
Perl 6: Delegation support is highly flexible, even allowing a regex to match method names that should be delegated to a particular object.
Prototype-based OO
Prototype-based OO has no classes. All objects are cloned from existing objects and modified. Requires lightweight singleton creation, without needing a separate class for every instance object. (Self, JavaScript, and Io are examples of prototype-based 00.) An example from Io:
Dog := Object clone # The Dog object is a clone of Object Dog tail := "long" # it has an attribute 'tail' with the value 'long' Dog bark := method("yap" print) # It has a method 'bark' Dog bark # call the method 'bark', printing 'yap'
Translation
The following list a set of languages, then within each language what the Parrot term translates to.
- Python
- Attribute A Python attribute maps to a Parrot property
- .NET
- Attribute What .NET calls an attribute Parrot calls a property
- Property What .NET calls a property we call an attribute
- Generic Terminology
- Instance Variable Instance Variables map to what we call attributes
Attachments
docs/pdds/pdd15_object_metamodel.png
Footnotes
None.
References
None.