Object
is a class), and Class
itself is a class. It's not as confusing as it sounds. You'll learn how the Telescript environment uses class objects (instances of Class
) to define other objects. You'll see how a Telescript engine stores and uses class objects and the class definitions that go with them, and you'll learn the fundamentals of declaring your own classes.This chapter, the first of this section, is strictly an overview to help you understand the fundamentals of custom classes and the syntax of class declaration. You'll find full details about class declaration in the chapters that follow.
It should come as no surprise to you, given Telescript's deeply object-oriented nature, that a class is itself an object--it is an instance of the class Class
. Each class that exists on a Telescript engine is a Class
object that defines any instances of that class.
Class
has a single attribute, name
, that stores a class name. A class name is an object that distinguishes one class object (one instance of Class
) from all other class objects.
The class name is an instance of the class ClassName
. A class name has a single attribute, classDigest
, that contains an octet string that denotes a class. That octet string is created by the engine when a class object is instantiated, and may be any octet string that the engine's current class-naming mechanism supports. Engines at this writing simply create an octet string that is the ASCII equivalent of the text of the class's name. For example, if you declare a custom class named "Traveller", the engine creates a class name for it whose classDigest
attribute is the octet string "54726176656c6c6572", the ASCII equivalent of "Traveller". It's important to note, though, that the class-naming mechanism may change with different implementations of the Telescript engine, and isn't guaranteed to reflect the text of the class name.
When you create a custom class, the engine instantiates Class
to create a class object for your class. It places the class object in a package of classes that you can include in the public packages of a place. Because the package of classes is a dictionary, it stores key-value pairs. Each class object in a class package is stored as a value. It's associated with a key that is the class name of the object so that the engine can easily retrieve the class by supplying its class name as the key. The public package containing classes, like all public packages is protected. It can be read but it can't be changed.
new
on the class object, which accepts any initialization arguments and returns a newly constructed instance of that class--an instance constructed according to the class definition associated with the class object.
Every object has a class
attribute that is the class object used to construct the object. When you use any of the object's features, the engine finds the object's class in the public packages and uses the class definition associated with the class to execute the method associated with the feature. Thus it's important that an object's class is in the public packages available in the place that the object occupies.
The scope of custom classes depends on the place where they're stored. Most custom classes have local scope--that is, they're only be available to the processes within one place (and subplaces thereof) of a single engine. That one place is typically the engine place, so the classes are available to every process in that engine--but not to processes on another engine.
As an example, consider Figure 12-1. The class CreditCheck
is one of many classes located in a class package that's one of Engine Place's public packages. That class has a scope that is Engine Place, Market Place, Gizmos Place, Gadgets Place, Information Place, Daily News Place, Weekly News Place, and all agents in those places--in other words, all places and all agents on the engine. The class ShipOrder
is one of several classes in Market Place. Its scope is Market Place, Gizmos Place, Gadgets Place, and all agents in those places. Its scope doesn't include Engine Place, Information Place and its subplaces, and any agents in those places. GatherNews
is a class of likewise limited scope--limited to Information Place, its subplaces, and all agents in those places.
Figure 12-1 : The scope of a class includes the place where it's posted, all subplaces of that place, and all agents in the place and its subplaces. (Rectangles are places, circles are agents.)
The idea of class scope brings up an interesting question in an agent-based language: what happens to an agent defined using local classes when that agent travels to a new location where those classes may not exist? The answer is: its custom classes travel with it. When go
is called on the agent, the agent ceases execution and the engine encodes the agent's state before transmitting the agent to a new location. As part of the encoding process, the engine encodes the custom classes (and associated class definitions) used to instantiate any of the objects that are part of the agent.
When the encoded agent arrives at its new location, the engine there decodes the agent's custom classes and includes them in the public place of the destination location. When the agent resumes execution in its new location, the custom classes it depends on will be available to it.
1 do{ 2 *@Place.publicPackages.include(Sampleclasses: module = ( 3 Speaker: class = ( 4 public 5 initialize: op() = { 6 ^; 7 }; 8 speakUp: op() = { 9 "Attention, please!".dump(); 10 }; 11 ); 12 )); 13 <<[:Speaker]speakUp>>; 14 }When you execute this script, it defines and creates the custom class
Speaker
, puts it in the public packages of a place, then instantiates the class and calls the operation speakUp
on the speaker object. You see these results:
String: <Attention, please!>Now let's take a look at the parts of the script and how they work.
do
block. The contents of the block start in line 2 with an operation call on a place. The call extends from line 2 through line 12. When executed, the operation includes the newly defined class in the public packages of a place. We'll ignore the call for the moment and concentrate instead on the single argument for that operation. That argument starts with the word SampleClasses
in line 2 and ends with the first parenthesis in line 12. The argument is a module declaration.A module declaration is a Telescript expression that includes class declarations and class interfaces (a truncated form of class declaration described later) within parentheses. When the declaration is executed, it returns a class package that can be included in the public packages of a place. The package returned is the kind of class package you just read about: it has as its values a class object for each newly defined class, and each of those values has a matching key that is the class name of the class object.
The syntax for a module declaration is this:
<ID>: module = (<class declaration(s)>)The identifier names the package that's returned when the module declaration is executed. The
module
keyword that follows specifies that this is a module expression. The classes declared in the module all appear as one or more class declarations within the parentheses of the expression. (You may also put class interfaces within the module, something you'll read about in Chapter 13, which discusses modules in detail.)Module
, and hence no module objects.The package returned by a module declaration does not put its newly defined classes into effect until it's included in the public packages of a place. That's not a function carried out by a module declaration. You typically include a module package into a place's public packages using an external Telescript programming tool such as a loader. The loader can include module packages in the public packages of an already executing engine place so the newly defined custom classes can be used anywhere within the engine place.
For our example script, we use a little chicanery that you probably won't use in real Telescript programming. We call the include
operation directly on the boot place. The boot place is a place created by an engine when you execute a solitary do block after compiling it. (At least that's what happens in the current implementation of Telescript--it's not guaranteed to happen in future implementations.) Because we know that the boot place is executing the contents of the do block, we can post the new class package in the public packages of the boot place. We do so in the second line of the example code:
*@Place.publicPackages.include(<module epxression> );The responder of this operation is
*@Place
. In other words, the responder is the current object (*
, the boot place) which is asserted to be a place (@Place
). Every place has the attribute publicPackages
, which is a list of public packages associated with the place. By calling the list operation include
on that attribute, the statement includes the package created by the module declaration into the public packages of the boot place. Any classes declared in the module declaration are now available within the boot place.Speaker
that contains two features: an initialize
operation and an operation--speakUp
--that simply dumps a string that says "Attention, please!" This simple declaration shows the main components of a class declaration.. All class declarations follows this syntax:
<ID>: <class characteristics> class <superclasses> = (<features>);
It's important to realize that the class ID of a class is not the same thing as the class name of a class. The class ID of a class is a string defined by Telescript syntax convention; the string identifies the class in source code. The class name is an object that distinguishes the class object of a newly created class from all other class objects. Although the current implementation of the Telescript engine uses the class ID to create a new class's class name (as described earlier), the mechanism used to create class names isn't guaranteed to do so, and may not in future implementations of the engine. The moral is this: don't expect the class ID to have anything to do with the string that is the class name.
When you choose a class ID for a class expression, you may not use the class IDs of built-in Telescript classes such as Object
, Agent
, and so on.
The keywords that control class characteristics are:
sealed
, which specifies that the class be a sealed class. If you don't use this keyword, the class is specified to be an unsealed class.
abstract
, which specifies that the class be an abstract class. If you don't use this keyword, the class is specified to be a concrete class.
mixin
, which specifies that the class be a mix-in class. If you don't use this keyword, the class is specified to be a flavor class.
abstract
and mixin
can't be used together--they're mutually exclusive. That's because a mix-in by definition can't be an abstract class.Here's an example:
Porcino: sealed mixin class = (<features>);The class
Porcino
is specified to be a sealed mix-in class. If you leave out all characterics keywords, you specify an unsealed concrete flavor class. That's the case in the example code, where speaker
is declared without any of these keywords.class
keyword of the class expression. The superclasses are enclosed in parentheses. If you use more than one superclass, their class IDs are separated by commas.You can specify superclasses for either flavor or mix-in classes; the conventions are a bit different for each kind of class.
Here's an example of a class declaration that declares a class that inherits from the flavor class Place
and the mix-ins MeetingPlace
and EventProcess
(in that order):
SwapMeet: class (Place, MeetingPlace, EventProcess) = (<features>);
TheWorks: mixin class (PackageProcess, PermitProcess) = (<features>);In this case, the new mix-in class
TheWorks
inherits features from the mix-ins PackageProcess
and PermitProcess
and then defines whatever other features it wishes in its declaration.You can specify a flavor class in a mix-in's inheritance by placing it as the first class in parentheses. When you do so, you don't specify a superclass, you specify the mix-in class's anchor class. A mix-in's anchor class restricts when that mix-in may be used in the inheritance of other class declarations: the mix-in may only be mixed in with its anchor class or with a subclass of that anchor class. To see how it works, consider this example:
Notifiable: mixin class (Agent, EventProcess) = (<features>);It specifies a mix-in class that inherits from the mix-in superclass
EventProcess
, and has Agent
as its anchor class. It may only be used to define a subclass (or subclasses thereof) of its anchor Agent
. In other words, it may only be used as a mix-in superclass of agent subclasses.If a mix-in specifies only an anchor class, the mix-in doesn't inherit from any classes (it's completely self-defined), and it's anchored to the specified flavor class. Consider this example:
Notifiable: mixin class (Agent) = (<features>);It declares a mix-in class that can only be used as a mix-in superclass of agent subclasses, as in the previous example. It doesn't inherit any features from other mix-in classes.
Object
as the new class's immediate superclass with no mix-in superclasses. If you leave out superclasses from a mix-in class declaration, the new mix-in class has no superclasses at all.
In the code example earlier, the flavor class Speaker
is declared without superclasses, so it inherits directly from Object
.
Because Telescript syntax is riddled with semicolons, and proper semicolon placement is important to writing compilable Telescript code, here are the rules for semicolon placement using feature elements: Operation declarations, attribute declarations, and sealing declarations must all end with a semicolon. Feature keywords do not end with a semicolon.
Operation and attribute declarations each have their own detailed syntax that we'll discuss in more detail later in this chapter. Feature keywords and sealing declarations are much simpler, so we'll talk about them here.
public
specifies public features--that is, operations and attributes that may be called by an instance of the class on itself or may be called by an outside object.
private
specifies private features--that is, operations and attributes that may only be called by an instance of the class on itself, not by outside objects.
If you use the keyword public
in the implementation of a class declaration, it remains in force until another requester keyword appears. All feature declarations that occur while public
is in force declare public features. If you use the keyword private
, it too remains in force until another requester keyword appears. All feature declarations that occur while private
is in force declare private features. For example, consider the following code snippet:
Foo: class = ( private <operation declaration> <operation declaration> <attribute declaration> public <attribute declaration> <operation declaration> );The keyword
private
in the second line means that the next three feature declarations declare private features. The keyword public
that follows overrides the first keyword, and specifies that the next two feature declarations declare public features.
Any feature declarations that aren't preceded by either private
or public
are declared by default to be private features.
instance
specifies features that are available only in an instance of the declared class, not in the class object created by the class declaration.
class
specifies features that are available only in the class object created by this class declaration, not in any instances of the declared class.
property
specifies that any attribute declarations that follow declare a property alone, not an attribute with getters and setters. (This keyword has no effect on operation declarations.) A property, as you'll read in later chapters, is accessible only to the methods defined by the same class that defines the property.
class
, instance
, or property
are declared by default to be instance features.instance
and class
. If you use the keyword instance
in the implementation of a class declaration, it remains in force until another responder keyword appears. All attribute and operation declarations that follow it are features declared for instances of the defined class.
If you use the keyword class
in the implementation of a class declaration, it too remains in force until another responder keyword appears. All attribute and operation declarations that follow it are features declared for the class object created by the class declaration.
To see how instance features and class features differ, consider the following example.
Widget: class = ( class <operation foo declaration> <attribute bar declaration> instance <operation fubar declaration> <operation rabuf declaration> );The keyword
class
at the beginning of the implementation is in force for the declarations of the operation foo
and the attribute bar
, so these two features are class features of the class object Widget
. This is the class object that is stored in the public packages of a place where the class is in effect.
The keyword instance
is in force for the declarations of the operations fubar and rabuf
, so both operations are instance features.
Once the class declaration is compiled and executed, the module package created containing the Widget
class object is included in the public packages of the engine place. Let's say that you create an instance of Widget
and name it gizmo
. You can call the operations fubar
and rabuf
on gizmo
because they are instance features and are available to any instances of the class Widget
. These operations are also available to any instances of subclasses of Widget
because the operations are inherited. If you try to call either the operation foo
or the operation bar
on gizmo
, you'll get a compiler error because those operations aren't instance features, and aren't available to instances of Widget
.
If you call the operation class
on gizmo
, you'll receive the class object Widget
as a return value. You can call either foo
or bar
on the class object, because they are both class features. You can't call either fubar
or rabuf
on the class object, because they are instance features that aren't available to the class object created according to the class declaration.
property
. If you use it in the implementation of a class declaration, it remains in force until another responder keyword appears. While in force, any attribute declarations that follow it are turned into property declarations. There are no class properties, so all property declarations declare instance properties.
Any operation definitions that follow the property
keyword ignore the keyword. The last class or instance
keyword that was in force applies to operation declarations while property
remains in force for attribute declarations. If, for example, if instance
is in force and is followed by the property
keyword, any operation declarations following property
declare instance operations. Any attribute declarations declare properties--until class
or instance
appear again.
As you'll learn later in this chapter, an attribute declaration has one or more IDs, a constraint (a class ID), and other optional attribute elements that can include a custom getter and setter. When the class is created, the engine creates a property to be used by the the attribute's getter and (optionally) setter. When the property
property keyword precedes an attribute declaration, the engine ignores everything in the declaration except for the ID (or IDs) and the constraint. The engine creates a property that is associated with the ID, an object that satisfies the type specified by the constraint of the declaration. You'll read more about how a property is declared later in this chapter and in Chapter <<<X>>>.
public
and private
, which define features, have no effect on property declarations because properties are not features.A sealing declaration begins by listing the IDs of any features to be sealed. Its syntax is the following:
<feature ID(s)>: sealed;The feature IDs at the beginning of the declaration are the IDs used at the beginning of each feature's declaration. You can use one or more IDs here. If you use more than one, you separate the IDs from each other using commas. Here's an example of a sealing declaration that seals three features:
hop, skip, jump: sealed;The sealing declaration is a useful convention for sealing many of a class's features at once, but beware of one pitfall: Many programmers read through feature declarations to understand how they can work with individual features of the class. If you seal a feature in a sealing declaration, it isn't at all apparent in the feature declaration that the feature is sealed. Another programmer reading the feature declaration may believe the feature is unsealed.
To accommodate the many aspects of an operation, the syntax of an operation declaration has many elements, some of them optional. We won't discuss them in full detail here, but we will take a look at the overall structure of an operation declaration. The syntax of an operation declaration is:
<ID(s)> : [<characteristics>] op ([args>]) [[<result constraint>] [throws <exception class ID(s)>] [= <block>];(The optional syntactic elements are enclosed here in square braces (
[]
) that aren't characters you use in an operation declaration.)The first thing to notice is that an operation declaration, like a class expression, is divided into its interface (its signature--its ID, the arguments it passes, the result it returns, any possible exceptions thrown, and so on) and its implementation (its method--a block of statements and variable declarations that execute when the operation is called). The two parts are separated by an equal sign; the signature is on the left of the equal sign, the method is on the right of the equal sign.
You can see an example of the simplest form of an operation declaration in a code snippet from the sample code at the beginning of the chapter:
speakUp: op() = { "Attention, please!".dump(); };In this example, the signature (the interface) is simply
speakUp: op()
. It uses no keywords and has no specified arguments nor a result nor any specified exceptions. Its method (the implementation) is a one statement block that dumps a string.Note that the implementation of an operation (the equal sign and the block that follows it) may be omitted. If so, you declare the interface alone of an operation. You can read more about operation interface declaration in Chapter <<<X>>>.
Some operation declarations use more than one ID, in which case they declare multiple operations, one for each ID. Each of the operations has a distinct ID, but a declaration that is otherwise identical to the other operations. For example, consider this code snippet:
speakUp, pipeUp, squawk: op() = { "Attention, please!".dump(); };It declares three operations:
speakUp
, pipeUp
, and Squawk
. Each operation does exactly the same thing when called: it dumps the string "Attention, please!"
abstract
, which specifies an abstract operation--one that can't be implemented (supplied with a method) in this class declaration. The operation must, instead, be implemented in subclasses of this class.
sealed
, which specifies a sealed operation--one whose implementation can't be overridden in subclasses of this class.
sponsored
, which specifies that the operation will be sponsored under the authority associated with the responder, not the authority associated with the requester.
abstract
keyword, the operation is concrete--it has an implementation that must be defined in this class declaration.
Sealed operations, as you read earlier, are exactly the opposite of abstract operations. They require an implementation in this class declaration, and the implementation can't be overridden in subclasses. Remember that you can seal an operation using a keyword here or by using a sealing declaration as described earlier. If you don't use the sealed
keyword, the operation is unsealed unless it's sealed in a sealing declaration.
abstract
and sealed
are mutually exclusive keywords; you can't use them together in an operation declaration because an operation can't be both abstract and sealed.
sponsored
can be used in combination with either abstract
or sealed
. Sponsored and unsponsored operations deal with authority, a topic that's covered in the section of this book that discusses processes. You can read about the specifics of sponsorship there. For now, it's enough to know that if you use the sponsored
keyword, you specify a sponsored operation. If you don't use the keyword, you specify an unsponsored operation.
op
element of an operation declaration. You may specify expected arguments within the parentheses--zero, one, or more arguments. If you want the operation to expect no arguments, you leave the parentheses empty as in the operation signature from the sample code before:
speakUp: op() = <method>;If you want the operation to expect arguments, you specify the arguments within the parentheses. You read an introduction to argument specifications in Chapter 4, where you learned to read operation signatures so you could supply the appropriate arguments when you called an operation on an object. We won't go into the full details of argument specification here--it's too involved for this overview chapter--but we will look at some examples and come up with some general rules.
Every argument specified within the parentheses of the operation declaration must have at least a constraint. A constraint, as you'll remember from Chapter 4, specifies a type (a class) that the argument object must be a member of, and a passage of the argument--whether it's passed by copy, by reference, and so on. Each argument may or may not have one or more IDs associated with it. If the argument has no ID, it's an unnamed argument. If it has one or more IDs, its a named argument (or arguments if it has two or more IDs). Look at an example:
readIt: op(firstPart, secondPart: copied Dictionary; thirdPart: List; Boolean) = <method>;This operation expects four arguments. The first two are the named arguments
firstPart
and secondPart
, both created by IDs associated with the constraint copied Dictionary
. This means that both arguments must be satisfied by objects that are members of the class Dictionary
, and that are passed as copies of the original objects. The third argument, thirdPart
, is a named argument satisfied by a member of the class List
that doesn't have its passage specified--therefore it must be passed by reference (the default passage). The fourth argument has a constraint but no associated ID, so it is an unnamed argument that must be satisified by a member of the class Boolean
passed by reference.You can specify a named or unnamed variable-number argument by using an ellipsis (...). The ellipsis follows the constraint; it specifies an argument that may be satisfied by zero, one, or more objects that satisfy the constraint. For example,
readIt: op(readings: Number ...) = <method>;specifies a named variable-number argument
readings
that can be satisfied with zero, one, or more objects that are members of the class Number
. A variable-number argument can be either named or unnamed.You'll see the reason for named and unnamed arguments in Chapters 15 and <<<X>>>, where you see how argument values are used in methods and for operation escalation. You'll find the full set of argument syntax rules in Chapter 15.
The method of the operation can supply the result using a return
statement (described in Chapter <<<X>>>.) If the method doesn't use a return
statement, the value returned is the value of the method itself (treating it as a block expression).
If you don't want to specify a result for the operation, you simply leave out a result constraint in the operation declaration. Note that if you don't specify a result for an operation and then you use a return statement in the operation's method, the compiler will emit a great squawk and refuse to compile your code.
throw
statement. You can specify what exceptions an operation may throw by adding the keyword throws
to an operation declaration, following it with the class IDs of one or more exception classes that may be thrown. The keyword and the class IDs come before the equal sign of the class declaration. If there is more than one class ID, they are separated from each other by commas. If you don't want to specify any exceptions, leave out the keyword and accompanying class IDs.
Specifying possible exceptions thrown by an operation isn't a rigorously defined part of the operation declaration. The exception class IDs you list are advisory only--the operation may throw any number of exceptions that aren't listed. For example, if you try to divide by zero within the operation's method, the operation will throw a DivisionByZero
exception, an exception that isn't created by a throw
statement within the method. The operation will throw that exception whether or not it's specified, and will throw it even if no exceptions are specified.
It's a good idea to specify any exceptions that may be thrown by throw
statements within the operation's method. This shows programmers using the operation what special exceptions they may encounter when using the operation.
Simply explained, arguments and results work like this: If, within a method, you declare a variable that has the same ID as a named argument in the operation's signature, that variable is assigned the argument value when the operation is called. That is, it does as long as you don't assign the variable a value when you first declare it. This syntax allows the method to receive the argument values necessary for its execution.
As discussed earlier, a method returns a result to the operation's requester by using a return
statement or (without a return
statement) by simply returning the value of the method itself. The returned object must satisfy the constraint specified in the operation signature.
Here's an example, an operation declaration that defines an operation that accepts arguments, returns a result, and may throw an exception:
1 sum: sealed op( figures: Number ... ) Number throws SumTooHigh = { 2 total: Number = 0; 3 for figureValue: Number in figures { 4 total = total + figureValue; 5 }; 6 if total > 1000 {throw SumTooHigh();}; 7 return total; 8 };The operation's interface is defined in line 1. Its ID is
sum
. It's a sealed operation that can't be overridden in subclasses. It specifies a variable-number argument, figures
, that can be any number of integers and real numbers (all members of class Number
). It also specifies a returned value that must satisfy the constraint Number
. It advises that its method may throw the exception SumTooHigh
.
The operation's implementation (its method) is defined in lines 2 through 7. Lines 2 through 5 make use of a variable, total
, and a for/in
block to sum up the numbers supplied for the variable-number argument figures
. Notice that the variable figures
isn't declared anywhere within the method. That's because every named operation argument is automatically declared as a variable for the method and is assigned whatever value (or values) were supplied for the argument. This is the mechanism that passes an operation's arguments into its method. It's discussed in more detail in Chapter <<<X>>>.
Line 6 tests to see if the sum of the figures is more than 1000, and throws an instance of the custom exception SumTooHigh
if so. This is the exception that was specified in the operation's interface.
Line 7 executes if the sum wasn't too high. It returns the sum of the arguments, a value that will be either an integer or a real number, which satisfies the returned value's constraint specified in the interface.
Declaring an attribute is simpler than declaring an operation. You don't have to worry about arguments because you can never call an attribute--you can only set it in an assignment statement, or read it in a feature expression. The syntax of an attribute declaration is this:
<ID(s)> : [<characteristics>] constraint> [throws <exception class ID(s)>] [with (<getter/setter declarations>)];Like an operation declaration, an attribute declaration has many optional syntactic elements, enclosed here in square braces. An attribute declaration in its simplest form consists of a single ID followed by a colon, a constraint, and a semicolon. For example,
keepTab: Integer;declares the read/write attribute
keepTab
as an integer that is passed by reference (the default of a constraint if no passage is specified). The attribute uses the default getter and setter, which simply read from or write to the property associated with the attribute.
The optional elements of an attribute declaration allow you to customize a standard attribute. The keywords let you create abstract, sealed, sponsored, or read-only attributes; the throws
clause lets you specify exceptions that might be thrown; and the with
clause lets you define a custom getter and setter.
abstract
specifies an abstract attribute--one whose getter and setter aren't implemented. The getter and setter must be implemented in subclasses of this class.
sealed
specifies a sealed attribute--one whose getter and setter can't be overridden in subclasses of this class.
sponsored
specifies that the attribute's getter (and setter if it has one) will be sponsored under the authority associated with the responder, not the authority associated with the requester.
readonly
, which specifies a read-only attribute--one that has a getter but no setter.
readonly
, are the same keywords you use with an operation declaration and have the same affect on an attribute's getter and setter (if there is one) that they have on an operation's method. abstract
and sealed
are mutually exclusive keywords; you can't use them together because an attribute can't be both abstract and sealed. The other keywords may be used together in combination. For example:
ridges: sealed readonly Integer;This statement declares a read-only attribute whose getter can't be overridden in subclasses.
throws
after the constraint, followed by the class IDs of one or more exception classes that might be thrown. If there's more than one exception class ID, you separate them with commas.This exception clause is advisory, just as it is in an operation declaration. It's typically not used unless you define a custom getter or setter.
with
clause at the end of the attribute declaration. The clause starts with the keyword with
followed by a pair of parentheses that encloses one or two operation declarations. These operation declarations follow the syntax you just read about earlier in the chapter. An operation here must have the ID get
(to create a custom getter) or set
(to create a custom setter). If you have two operations, one must be get
, the other set
.
Custom get
and set
operations must satisfy the constraint set by the attribute declaration. You can read full details about custom getters and setters in Chapter <<<X>>>.
property
in a class declaration, any attribute declarations that follow while the keyword is in force become property declarations. If that's the case, then only the ID (or IDs) and the constraint of the attribute declaration is considered. Each ID declares its own property.A property takes the type specified by the constraint, but it ignores the passage. That's because all properties are passed by reference.
Properties can be used by the methods of a class, but can't be used by the methods of subclasses--a convenient aspect of scope that you can read more about in Chapter 16 when you read about methods.
Class
and ClassName
. The class tree that follows shows their inheritance:Object
* Class (Protected)
* ClassName
Class
is defined using the mixin Protected
which, as you'll recall, immediately locks any instance of a subclass so that it can't be changed.Class
, as you read earlier in the chapter, defines a class object that defines other objects--the instances of the class. To be used for object instantiation and definition, class objects must be contained in a public package of a place where the instantiated objects will reside (or in the public package of a place containing that place).Class
, it throws the exception FeatureUnavailable
. That's because class objects may only be instantiated using the class declarations described earlier in this chapter--syntax that ensures that the class object of a newly defined class is included in a package that can be included in the public packages of a place.You can, if you wish, copy an already constructed class object. If you do so, the copy has the same inheritance as the original class object.
Class
defines a single attribute to hold the class name object:
name
is a read-only attribute that holds a class object's class name.
Class
offers two operations that let you check the inheritance relationship between two classes:
isSubclass
accepts a class object as its sole argument. It checks the responder class object against the argument. If the responder is a subclass of the argument, or if the responder is the same as the argument, the operation returns the Boolean true
. Otherwise it returns false
.
isSubclassByName
accepts a class name object as its sole argument. It checks the responder class object against the class associated with the class name argument. If the responder is a subclass of the argument-specified class, or if the responder is the same as the argument-specified class, the operation returns the Boolean true
. Otherwise it returns false
.
25.class.isSubclass(Number)returns
true
because25 is an integer, and Integer
is a subclass of Number
.Class
offers one operation that lets you instantiate the class:
new
accepts a variable number of arguments, the parameters necessary to initialize the new object. (These parameters are defined by the arguments of the class's initialize
oepration.) The operation returns the new instance of the class; the instance's state is defined by the parameter arguments.
ClassAbstract
if the class is an abstract class and can't be instantiated. If the class's initialize
operation fails, it can throw the exceptions ObjectUninitialized
or Exception
.
As an example of instantiation, consider the following operation expression where new
is called on the class object String
:
String.new("Instant Instance")The expression returns a string object that has the contents "Instant Instance". "Instant Instance" was the argument used to initialize the new string object.
If this example looks familiar to you, consider this constructor expression:
String("Instant Instance")It accomplishes the same instantiation task as the previous expression. As it turns out, the syntax of a constructor expression is a convenient syntactic shorthand for calling the
new
operation on a class object. The two expressions are identical in execution and result.ClassName
defines an object that distinguishes an associated class (its subject class) from all other classes, whether they're built-in or custom classes. This is not a class that most programmers instantiate--it's typically used by the engine to create a class name object for a new class object created by a class declaration. ClassName
has no operations, and a single attribute:
classDigest
is an owned attribute that is an octet string. The octet string denotes the class name's subject class.
classDigest
attribute. The Telescript engine uses a particular algorithm for choosing the appropriate octet string for a subject class. Because the algorithm isn't defined as part of High Telescript syntax, programmers shouldn't try to anticipate it. It's simplest to leave class name assignment to the engine. It's a rare case, in fact, that programmers will have any need to instantiate class names for themselves.
Generated with Harlequin WebMaker