Previous page Table of Contents Next page
Classes:
Every piece of data, without exception, is an instance of a class. There are no magic "primitive" data values that work differently from everything else.
Memory management is dynamic and safe. Only the garbage collector can deallocate memory. It is impossible to make a "wild" memory reference at run time. (There are special intrinsics defined in a special module which allow writing unsafe code, used only to write some of the runtime.)
Typing is dynamic. That is, objects have types. The type of an object is simply its class. An object is a member of its class and also is a member of each type from which its class inherits.
Definitions, slots, and method return values have types too, but these are just a restriction on what objects can be the value of a definition, the value of a slot, or the result of a method. These declared types are more general than object types: they can be any type of type: a class, a protocol, an integer range, or a union of declared types.
The false value has its own class (a subclass of boolean). This allows for the very common pattern of a declared type that is either a specific class or false. The true value has its own class too.
A class has exactly one superclass, except for the root class named anything which has no superclass. Multiple inheritance is ruled out primarily because it would create problems in determining whether two classes are disjoint, and secondarily because it would make compilation of slot access more difficult. But if you need multiple inheritance, see protocols below.
By convention the name of a class prefixed by $ is defined to the class object.
Constructors:
A class has exactly one constructor, unless it is an abstract class, which has no constructor. Calling the constructor is the only way to create an instance of a class. A class's constructor is a method for a function which by convention has the same name as the class (without the $ prefix).
Sometimes the actual constructor is internal and is given a different name, and an ordinary method for a function with the name of the class appears to be the constructor but is implemented by calling the real constructor. This provides increased convenience in using a class that can be constructed different ways. The ordinary method is called a pseudo-constructor.
Defining an abstract class does not define a constructor, but it is often useful to define a method for a function with the same name which will construct an instance of a subclass.
Slots:
A class has one or more slots. A slot can be single-valued or multi-valued, and can be fixed or assignable. You access a slot by calling a reader or writer method to read or write the slot respectively. When writing in a class-oriented style you can invoke these or any other functions via a special "dot" syntax. Each slot has a declared type; all values of the slot must be members of that type.
Thus the slots of a class define the structure of instances of that class.
You access an individual value of a multi-valued slot by using an additional argument which is a subscript. The number of values of a multi-valued slot in an instance of a class is fixed at the time the instance is created.
You cannot define a slot with the same name in the same module as an inherited slot. More precisely, the slot reader methods for two slots of the same object cannot be methods of the same function, because reader and writer methods are always sealed. If you need to override inherited characteristics, use a function with different unsealed methods applicable to each class.
Every object has a fixed, single-valued slot named class whose value is the actual class of the object. This is declared in the root class anything. Like any slot you cannot hide it. In an implementation it might not really be a slot, but it behaves the same as a slot.
Protocols:
A protocol is a type that consists of nothing but a name and an associated set of method requirements. The semantics of a protocol type come from the methods and method requirements that have parameters of that type. The members of a protocol are the objects whose class inherits directly or indirectly from that protocol.
A method requirement is a requirement that an applicable method always exists for a specified function when applied to arguments of specified types. There could be one general method that works for all members of a specified type, or there could be specific methods for each non-abstract class that implements the type.
A protocol defines behavior of the objects that are members of the protocol, without regard for the structure of the objects. A class defines the structure of the objects that are members of the class, and also can define behavior when there are methods that are applicable to instances of the class.
A class inherits from one other class (except for anything, which inherits from nothing) and from zero or more protocols. A protocol inherits from zero or more other protocols. New protocols can be added to a class at runtime.
When there are multiple applicable methods in a function call, the method that is invoked is the unique least general method. When multiple inheritance of protocols is involved, there could be more than one least general method; this is a run-time error.
Anything type:
Every object is a member of the type anything. Every type is a subtype of anything.
Something type:
The type something has no members. It is only useful in connection with method requirements. Something is a subtype of every type. It is actually the degenerate case of a type union.
Null Pointers:
There is no "null pointer". However, "instance of class x or false" is a frequently used type that allows false to be used like the traditional null as an indication that no instance of class x is present. This is simply a type union of x and the false type. Note that false is different from the empty list.
Uninitialized Values:
There is a special "uninitialized" value which is not a member of any type. It is only used as the value of a definition or slot that has not yet been initialized. Reading the "uninitialized" value signals an error. A definition or slot cannot be reset to this value, so we only need to check for this value when reading a slot that is not initialized in the class definition and when reading a definition that could be read before its initial value expression has been executed. This "uninitialized" value takes on some of the roles of the null pointer in some other languages, but cannot be mentioned in PLOT code (except in special unsafe runtime code).
Object Representation:
The fact that every piece of data is an instance of a class implies that pointers do not need to be tagged if all classes are represented the same way. But an implementation might choose to have a special representation for small integers to decrease garbage collection overhead, either using reserved address space or using tags.
Some classes are "unboxable," which means the compiler knows how to represent the value of such a class directly, rather than as an object. This is purely a representation choice; all the object-oriented operations continue to work on unboxable objects. Instances of unboxable classes can be freely copied and thus lose their identity, therefore such classes are immutable.
A slot whose type is unboxable is stored in unboxed form rather than as a reference to a boxed object. Many intrinsic methods are compiled inline when the arguments are unboxed. I'd like to make unboxability extensible but probably it is built into the compiler. The garbage collector will need to be told about unboxed slots, registers, and stack items.
The class integer is an abstract class with at least two concrete subclasses. The subclass for 32-bit integers is unboxable, perhaps there is also an unboxable subclass for 64-bit integers. The subclass for arbitrarily large integers is not unboxable. The compiler maps integer range types to subclasses of integer that can contain them.
Previous page Table of Contents Next page