This chapter introduces you to the basics of initialization so you can understand what values you'll need for initialization and how those values are used. You'll learn about initialization arguments and escalation in initialization. To help you instantiate predefined Telescript classes, you'll see how a class description lists necessary arguments. And--because some of the objects you create may be the products of class families--you'll learn how to derive a class from a class family and then instantiate that class.
When the Telescript engine creates a new object by instantiating a class, it must provide a value for each of the new object's properties. Left to its own devices, the engine simply provides a nil for each property. A set of nil properties may be okay for some newborn objects, but objects of many classes must start their lives with properties set to non-nil values. The Telescript mechanism that provides for a set of non-nil values on an object's birth is the initialization operation.
initialize
. The class may define the operation itself, or it may inherit the initialization method from its immediate superclass. The initialization operation may or may not require arguments. Its job is--at its discretion--to set the value of any of the new object's properties. The operation may set a property to a default value, an argument value, or a calculated value. If the initialization operation doesn't set the value of a property, the property has a value of nil. Abstract classes, because they are never instantiated, don't need to have an initialization operation. They may have one, however, to set properties when initialization is escalated as described later in this chapter.You'll learn the intricacies of defining an initialization operation when you learn to define your own classes in the next section of this book. For now, it's enough to know in general what an initialization operation does.
String
or BitString
). You can only instantiate primitive classes using a literal expression; the compiler will balk if you try to create a primitive using a constructor expression. When the engine executes a literal expression, it initializes a corresponding object with the value contained in the expression.All non-primitive non-abstract classes can be instantiated using a constructor expression. A constructor expression accepts zero or more arguments and, when executed, starts the process of initialization. Its syntax is:
<class name>(<optional arguments>)The class name specifies the class you want to instantiate. The parentheses enclose initialization arguments which, as operation arguments, must be separated by commas if there are more than one. It's possible to have no initialization arguments, in which case the parentheses are empty.
When a constructor expression is executed, the engine instantiates the specified class, setting all properties to nil. It then calls initialize
on the newly created object using the arguments you supply in the constructor expression. You must provide the proper arguments for initialization just as you must supply the proper arguments for any other kind of operation. The following example shows a constructor expression that constructs an instance of the custom class TestObject
using the initialization arguments 75
and true
:
TestObject(75, true)
initialize
on an object, the initialize
method accepts any arguments provided and then executes. The initialization method for each class, no matter how it's written, must at one point escalate the initialization operation--that is, the method calls the initialize
method of the object's immediate superclass. When the escalated operation finishes, execution drops back to the method of the object's own class. (Chapter 4 discusses escalation in more detail.)Because the initialization method of each class must escalate intialization, initialization chains up from subclass to superclass to superclass to superclass, then back down again as each initialization method finishes execution. Initialization occurs at each level of the chain.
As an example, consider the class tree in Figure 8-1. If you call initialize
on an instance of the class at the bottom (StocksRecords
), the initialization operation at some point escalates to the superclass FinancialRecords
. As the FinancialRecords
initialization executes, it escalates at some point to the mix-in superclass Currency
, which escalates to the superclass DataRecorder
, which in turn escalates to the root class Object
. When the initialization method of Object
finishes, execution returns to the lower classes in the inverse order of escalation: first to DataRecorder
, then to Currency
, then to FinancialRecords
, and finally back to StocksRecords
. When StocksRecords
' initialization method finishes execution, initialization is completed.
Figure 8-1 : Initialization escalates from subclasses up to superclasses as shown by the numbers here.
Why must initialization always escalate through an object's superclasses before an object is initialized? Because, as you may remember, a class's methods can only set the properties of the class itself. They can't set the properties of any of the class's superclasses or subclasses. This means that when you instantiate a class, the class's initialize
operation can only set the class's own properites; it can't set any of the properties it inherits from its superclasses. By escalating initialize
to the method of each of its superclasses, the escalated initialize
can set properties for each superclass. The newly-instantiated object will have all of its properties--native and inherited--set appropriately.
In the previous escalation example, when you instantiate StocksRecords
, its initialize
operation sets StocksRecords properties. When initialize
escalates to FinancialRecords
, it sets FinancialRecords properties. Each level of initialization sets properties of the current class until the Object properties are finally set and escalation returns back down through the inheritance hierarchy.
Escalation is a topic of exquisite intricacy that involves issues such as canonic order (the order of escalation through superclasses). We won't get into the details in this chapter; you can read about them in Chapter <<<yet to be written>>> where you learn to create your own methods. For now, it's enough to know that whenever you create an object, its initialization works at the level of each of its superclasses. Which leads to some interesting questions about the arguments you provide for initialization...
initialize
operation of each class has its own required arguments, which may range in number from many to none. When you instantiate an object, you supply one set of arguments. How are these arguments passed on to each level of escalation?
The Telescript engine maintains a LIFO stack of arguments for initialize
. It reads the provided arguments from right to left and pushes them onto the stack (which means that the first argument is at the top of the stack). When initialize
runs for the instantiated class, the initialize method pops off the arguments it requires and leaves the other arguments on the stack. When initialize
escalates to the method of the immediate superclass, that method pops off the arguments it requires. Each level of escalation pops off the appropriate number of arguments from the top of the stack until escalation is finished and all the arguments are used.
Not all escalations are as simple as the account above--you'll find some interesting wrinkles in different classes. Many superclasses require no arguments, so they take no arguments off the stack before escalation. This simplifies the number of arguments required for construction. Some superclasses have no initialize
operation at all. In this case, the engine simply escalates initialize
to the next superclass, passing the untouched stack with it. A few classes use their initialize
method to calculate one or more values for escalation, then push the values on the stack for escalation. In such a case, you won't supply those initialization values in the constructor expression.
initialize
under the heading "Constructor." This signature, like any other operation signature, shows the argument identifiers and constraints necessary for initialization. (These arguments include those necessary for escalation.) It may also show exceptions that can be thrown on initialization.The Constructor section of a class description explains the meaning of the initialization arguments required for that class. You may also see a table of the class's attributes, each associated with a default value and an argument that sets a new value. If you don't set these attributes (in other words, you supply a nil for their arguments or the attributes are not allowed as arguments), they are set to their default values.
Arguments that are provided for escalation to superclasses aren't always fully explained in the class description; you'll find explanations for these arguments in the Constructor section of the appropriate superclasses.
You may occasionally come across a class description with no Constructor section. In this case, the class's initialization operation is the same as that of its immediate superclass--so you can find constructor information by looking up the superclass. Or the class may be an abstract class, in which case you can't instantiate the class anyway.
initialize
arguments.You can find full information about class definitions and operation signatures in the next section of this book <<<yet to be written>>> where you learn to create your own custom classes.
Collection
, which you'll learn about in detail in the next chapter. It's used to derive a variety of parallel classes, each of which can hold objects of a different type and compare those objects in a specified way.When you create an instance of a derived class, you must first derive the class from the class family. You can then provide initialization arguments for the object. To derive a class from a class family, you provide an actual parameter for each of the formal parameters defined by the class family.
A class family definition starts with a list of formal parameters that it uses throughout the rest of the definition. Each formal parameter is a unique identifier that can be used to specify a class almost anywhere in the class family definition. You can think of the formal parameter as a placeholder--a class variable, if you will. The classes within the class family definition that are specified with formal parameters aren't determined until a class is derived from a class family.
When you derive a class from a class family, you supply one actual parameter for each formal parameter of the class family. Each actual parameter is a class, which you specify with a class name. When the engine derives a class, it creates the class definition by copying the class family's definition and replacing any formal parameter with the class supplied by the corresponding actual parameter. The resulting derived class has no formal parameters; the classes are all specifically defined using the actual parameters.
<class family name>[<one or more actuals>](<optional arguments>)The class family name is followed by a pair of square brackets ([]) that contain actuals. You may have one or more actuals--it depends on the definition of the class family. You must have one actual parameter for each formal parameter of the class family. The actuals are listed in the same order in which their corresponding formals are defined; the actuals are separated by commas.
A pair of parentheses follows the square brackets. These enclose any arguments necessary for initializing the derived class, and may be empty if there are no arguments.
As an example, consider the Telescript class family Collection
. It has two formal parameters: Item
and Match
. The formal parameter Item
is the class of the objects that may be contained by a Collection object. The parameter Match
stands for the mix-in class that determines how objects in the collection are compared. If you derive a class from Collection
, you might supply the actual parameters Number
and Equal
as shown in the following constructor:
Collection[Number, Equal](36, 100.5, 5, 27)This expression uses the actuals
Number
and Equal
to derive a class from Collection
. The derived class is referred to as Collection[Number, Equal]
. The class Number
(the first actual parameter) satisfies the formal parameter Item
, so the class is defined to contain a collection of numbers (objects that are members of the class Number
). The class Equal
(the second actual parameter) satisfies the formal parameter Match
, so the class is defined to compare its collection objects in the way defined by the Equal
mix-in (as opposed to the way defined by the Same
mix-in, one of the other classes you can use for the second parameter).
The expression ends with a set of four numbers in parentheses. These numbers are the arguments required for initializing a Collection-derived class. They become the objects contained by the instance of Collection[Number, Equal]
created by this expression.
[]
). Take a look at the Telescript class family Collection
as an example. The first part of its class signature is
Collection: interface[Item: Class; Match: Class<:Compared]The declaration of the formal parameters within square brackets are separated from each other by semicolons (;). Each declaration starts with an identifier followed by a colon and the key word
Class
, which is the beginning of a constraint that specifies what classes can satisfy the formal parameter. In this example, there are two formal parameters: Item
and Match
. Item
is followed by the constraint Class
, Match
is followed by the constraint Class<:Compared
.
If a formal parameter has a constraint that has no class listed--just the keyword Class
followed imediately by a semicolon, then the formal parameter can be satisfied with any class--flavor or mix-in. The formal parameter Item
in the previous example is a case in point. It ends in Class;
, so it can be satisfied with an actual parameter that is any class.
If a formal parameter has a constraint that lists a class following the keyword Class
and the symbol <:
, then the actual parameter is constrained: it must be the class itself or one of its subclasses. The formal parameter Match
in the previous example uses this convention. Because it ends in <:Compared
, the actual parameter is constrained to a class that is either Compared
or a subclass of Compared
. This means that the formal can only be satisfied by Compared
or either of its two subclasses, Equal
or Same
.
Note that a class family signature can list two or more formal parameters with a single constraint, just as an operation signature can list two or more arguments with a single constraint. Look at the first part of the class signature for the class Dictionary
:
Dictionary: interface[Key, Value: Class; Match: Class<:Compared]The two formals
Key
and Value
both share the constraint Class
.Collection[Number, Equal]
, the initialization arguments for the class must be members of the class Number
because the derived class is defined to handle Number objects. If you derive the Collection class Collection[Character, Equal]
, the initialization arguments must be members of the class Character
, not class Number
.
If you look at the initialize
operation signature in a class family description, you may sometimes find that a constraint for an argument is defined using a formal parameter. When you derive a class, that constraint takes the class of whatever actual parameter you supplied for the formal parameter. When you supply initialization arguments, you must make sure they match the actual constraints of the derived class.
As an example, consider the initialize
operation signature for the class family Collection:
initialize: op (items: Item ...);The argument
items
is constrained by the formal parameter Item
, which was presented in the class signature you looked at earlier. If you derive the class Collection[Number, Equal]
from the Collection class family, items
' constraint is set to Number
, so the variable arguments you supply for items
must all be members of the class Number
.Object
no matter how the formal parameters are constrained. For example, a custom class family with this at the beginning of its signature
Census: interface[Unit: Class; Population: Class<:Number]has two formals:
Unit
, which is constrained to any class (flavor or mix-in) and Population
, which is constrained to Number
or either of its two subclasses, Real
or Integer
. If you instantiate the family without providing actuals (that is, you leave out the square brackets and anything they might enclose), then you realize a class using the family's default actuals: both are Object
. For example, this constructor expression
Census(67, report)derives the class
Census[Object, Object]
initialized with the values 67
and report
--even though the formal Population
is constrained to be a number, not an object.Class
alone, in other words) has a default of Object
. Any constraint with a specified class (Class<:Compared
, for example) has a default that is the specified class (Compared
for the last example).As a real example, consider the first part of the Collection class signature once again:
Collection: interface[Item: Class; Match: Class<:Compared]The default actual parameter for
Item
is class Object
; the default actual parameter for Match
is the class Compared
. If you use Collection
in a constructor without specifying actuals as in this example
Collection('A', 5, 2.3)you derive the class
Collection[Object, Compared]
and pass three objects as initialization arguments.
Collection
and its subclasses.
Generated with Harlequin WebMaker