David L. Shang
Object-oriented programs create objects. But how can we access to these objects?
An object may be created in the memory for temporary use, in a persistent storage to outlive the creator (program), or in a network server to be shared by many people. Objects must have places to live, and they require different kinds of living places, depending on the purpose of the creation.
It would be so nice to forget about the memory, the disk, and the network; and just focus our attention on objects themselves. For example, let blue_sky be an image object. We would like to use the object by an expression
blue_sky.drawSelfOn(myWindow);without worrying about where the image object is placed. It might live in the local disk, or perhaps in an internet server thousands miles away. But we don't care. Once blue_sky becomes valid, the only thing we should know is that it is an image object.
In traditional programming, things are not that simple. To get an image object from a network server, we have to do the following:
open an URL connection, say, X; read the (binary) contents from X; check whether the binary is in an image format; create an image object in the memory, say, blue_sky call blue_sky.drawSelfOn(myWindow); free blue_sky; close the connection X.
What we would like to have is the following:
blue_sky: URL of Image = "http://www.foo.com/images/blue_sky.jpg"; if (blue_sky) blue_sky.drawSelfOn(myWindow);
We do not need to know the concept of network connections; and we should never worry about remembering clean-ups such as closing a network connection and freeing the temporary memory. Let the system take care all the difficult things.
To call a person, we have to use the person's name. To visit a house, we must have its address or its location. To access an object, we should also use a reference to which the object is attached. The reference constitutes a distinctive name of a single object or a group of objects. We call the reference the object name.
In traditional languages, identifiers introduced by declarations can be viewed as names. Consider examples in C++:
class Image {...};
Image x;
Image* y;
The name Image represents a class; x is name representing an object of Image; and y is a name representing an object of Image but uses a dynamic memory management model.
The concept of name in a traditional language is built-in. There are constant names, variables, pointers, or smart references with garbage collections. Those built-in names can never satisfy various application requirements. There has been a long debate in history that whether a language should provide smart references or not. But garbage collection should not be the focus of the debate. Indeed, a language should provide smart references with garbage collection for a leek-free memory management. But the presence of smart references should not disable other options. Smart references are not the whole solution. Some long-living objects cannot be garbage collected even though they are dangling for the time being. Some mobile objects cannot contain any references relying on the local memory address space; they should package themselves as closures to travel in the network space.
The concept of name in Transframe is not built-in. It is unified into the concept of object. That is, a name itself is an object. It is an instance of a referential class.
A declaration introduces one or more names in a scope. It regulates the range of objects which a name can represent, prevents the name from referring to an object of the wrong type, and possibly, associates an initial object to the name. Consider the following example:
x1: Image; x2: static of Image; x3: pointer of Image; x4: URL of Image; x5: Persistent of Image;
All names declared above are names representing an image object. But they are different names. Names declared from the first line to the third line use built-in Transframe reference classes: x1 is equivalent to a smart reference with garbage collection in languages like Eiffel and Java; x2 is equivalent to a variable in languages like C++; and x3 is equivalent to a memory pointer in languages like C++ and Pascal, which should be used only for low-level system programming; Names x4 and x5 use user-defined reference protocols. x4 is a remote reference pointing to an image located in the internet. URL is a user-defined reference class for locating web resources. x5 is a persistent reference pointing to an image residing in a persistent storage. Persistent is a user-defined reference protocol for locating objects wrapped in an object database.
Referential is an abstract class. All names declared by declarations are instances of a subclass of referential. The referential class is parameterized by the type of the object that can be associated with the name. The type is the object type of the referential class. The abstract referential class is defined as below:
interface class referential #(sealed ObjectType: type) {};
Transframe has four built-in sub referential classes. They are type, static, smart, and pointer.
A type reference is a name representing a type (class). Other references are names representing objects.
A static reference is a name permanently bound to an storage that is allocated to hold the state of an object at the time of the declaration of the name. The location of the object container of an static name cannot be changed. Static references provides a built-in implementation for static or by-value names. They hold distinct object and never share objects with other static names. For example:
x: static of Image; y: static of Image; ... x:=y;After the assignment, x holds a distinct copy of the image held by y.
A static name is similar to a C++'s variable, but not exactly the same. A static name can be either type-exact or polymorphic. When it is type-exact, it can only hold the object instantiated exactly form the declared class; when it is polymorphic, it can hold an instance of a subclass of the declared class. But a C++'s variable is always type-exact, instances of sublclasses must be sliced to the declared class before the variable can take the instance.
A smart or a pointer reference is a built-in dynamic name which points to a dynamically allocated piece of storage to contain the object, and the location of the storage can be changed during the life-time of the name. Same kinds of dynamic names can share objects. Consider two smart references:
x: Image; y: Image; ... x:=y;After the assignment, x holds the same image pointed by y.
Smart references provide an automatic memory reclamation, enabling a memory-leakage free programming environment. Pointers are "stupid" names that does not guarantee free of dangling objects or invalid references, which should only be used in low-level programming, for example, in the implementation of garbage collection, or when the absolute memory address is required for programming hardware devices.
By value replacement, the object contained in the destination name is replaced with the object contained in the source; by copied reference replacement, the destination name creates a new copy of the source; and by shared reference replacement, the destination name represents the same object denoted by the source name. The following figure shows the default replacements used for different name combinations.
Traditional languages like Eiffel and Java only support value replacement (for primitive objects only) and shared reference replacement (for composite objects only). In practice, copied reference replacement is highly desired when object must be copied from one name to another name of the different type, for example, to copy an object from a database into memory, or write the object back to the database.
In Transframe, the type of replacement used depends on the type of name, not the type of the object. Therefore, a composite object can also use value replacement via static names. Some languages, such as Sather, recognized the usefulness of transferring composite objects by value, and introduced the concept of value classes. A concrete leaf class can be declared as a value class and variables of the value class will not use smart references. Transframe's integrated name concept provides a more general and flexible way. Static names are not limited to concrete classes, needless to mention leaf classes.
Name creation is a process to make the name ready to which an object can be attached. The creation of a type name, a static, smart, or pointer name is interpreted by Transframe. A user-defined referential class has to define the constructor if specific name creation protocol is required.
Consider an example:
class URL is referential
{
private:
obj: ObjectType;
public:
enter (path:char[]) // constructor
{
// get the URL resource via the path and
// attach it to its private smart reference
obj := WebSearch(path);
}
};
and the following usage:
app: URL of TFApplet ="http:://www.foo.com/apps/hello.tf";
The name app is created by calling the URL's user-defined constructor.
Name deletion is a process to destroy a name.
A type name will destroy the type represented by the name before itself is destroyed.
A static name will destroy the object contained in the name before itself is destroyed.
A smart name will release the attached object before the name is destroyed; if the released object becomes dangling, the object is destroyed.
A pointer name will be destroyed without doing anything to its attached object.
A user-defined reference may need a clean-up, for example, an unlock procedure or an update of the value in a secondary storage after a modification in the cash. In this case, a destructor should be defined.
Consider a referential class:
class file is referential
{
public:
enter (name: char[])
{
//open the file and check its type
};
exit ()
{
if (not_saved) popupConfirmDialogForSave();
};
};
and its usage in a block:
{
image_file: file of Image = "photo.jpg";
image_file.convert(GIF_IMAGE_FORMAT);
}
The file name is created by calling the file class' constructor that takes a character string; and the object attached to the name is a JPEG image contained in the file "photo.jpg". Afterwards, the image is converted to GIF format, but the conversion is done only in the cash held by the image_file reference. At the exit point of the block, the name image_file is destroyed, and its destructor is called. A pop up dialog will be created to ask whether the unsaved image should be written back or into a new file.
Name attachment is to make name to represent an object. There are various ways to attach an object to a name. The type of the object that can be attached to a name is described by Transframe's name attachment rule, which is more permissive than the rule found in other languages. I will explain this in my future column.
User-defined referential class may specify three operators for name attachment: new, delete, and share. Suppose that R be the user-defined reference, operator new, delete, and share have the following interface:
class R is referential
{
operator new(): ObjectType; //or pointer of ObjectType
operator delete();
operator share (R);
};
The operator new and delete is used for the copied reference attachment. When the name has to create a new space to put a copy of the object to be attached, the operator delete is first called, which gives the name a chance to detach the previously attached object, if any. Then the operator new is called, which should return a smart reference or a pointer, which is used to put the new copy of the object to be attached.
The operator share is used for the shared reference attachment. When the name is going to share the object currently attached to another name in the same referential class, share is called; so that necessary sharing information on this object can be updated.
Depending on the nature of the name, these three operators may or may not be given. For example, if the name does not support shared reference replacement, then, the operator share should not be implemented.
These three operators are transparent to users. The only operator available to users is the name assignment. Users of a name are not necessary to know the detail under an assignment.
Transframe support simple name assignment and tuple assignment.
Consider the usage of the URL name:
r1, r2: URL of VRMLObject; r1 := "http://www.foo.com/vrml/models/view1.vrml"; r2 := r1;
The first assignment is actually a name creation, which initializes the name r1 by calling the constructor of URL, i.e. URL("http://www.foo.com/vrml/models/view1.vrml").
The second assignment is a shared reference attachment, it attaches the object referred by r1 to r2. To make this attachment possible, the name attachment rules (here subtype rule is used) must be satisfied, i.e., the object type of "r2" must be a subtype of the object type of "r1". Also, the class URL should have the share operator defined:
class URL is referential
{
private:
obj: ObjectType;
public:
operator share(other: URL of ObjectType)
{ obj:=other.obj; };
};
A name assignment can be a list name assignment (tuple assignment) in which the left side of the assignment is a list of names. Here is an example:
(r1, r2) := (r2, r1);
Member selection is used to access a member object or a member class through a member name. Member selection is done by a member selection operator ".", and must be done through a name.
Object member selections based on the four built-in references (type, static, smart, and pointer) are interpreted by the Transframe language.
Selection of members defined in the name itself is also interpreted by Transframe.
User-defined names must provide the member selection operator for specific member selection protocols. The operator should convert the user-defined reference into a smart reference or a pointer or a list of such references; so that Transframe can perform a member selection based on the provided built-in references.
Consider a user-defined reference:
class URL is referential
{
private:
obj: ObjectType;
public:
operator.(): ObjectType { return obj; }
enter (Path:char[])
{
// get the URL resource via the path and
// assign it to its private reference
}
};
and the following usage:
app: URL of TFApp = "http:://www.foo.com/apps/hello.tf"; if (app) app.run();
A URL reference is declared and is initialized to refer to a Transframe application located at a given URL site. The member selection used at the following code will call the member selection operator defined in the URL class, which returns a smart reference of TFApp. And then, Transframe will select the member class defined in TFApp based on the returned reference.
The operator "." may return a stream. Consider an example:
class team is referential
{
private:
members: ObjectType...;
public:
operator.(): ObjectType...{ return members;}
enter (x: ObjectType...) { members = x; }
};
Let PuppetBear, PuppetLion, and PuppetTiger be subclasses of Puppet which implements the dance member function. The following usage:
dancing_team: team of Puppet; dancing_team := (PuppetBear(), PuppetLion(), PuppetTiger()); dancing_team.dance(aDancingScript);
will make each member in the team to dance under a dancing script. The operator "." returns a stream of puppets, and an dance activity is created in each member of the team.
The selected member can either be a member of the name itself or the member of the object referred by the name. Let's add a new method in the class team:
class team is referential
{
public:
welcomeNewMember (x: ObjectType);
...
};
and the expression:
dancing_team.welcomeNewMember(PuppetBuffalo());will add a buffalo into the dancing team. The member selector selected a member defined in team, rather in the object referred by team; therefore, the specific member selection protocol defined in team is not used.
In case of a member name conflict, the member defined in the name overrides the name defined in the object. For example, if the class team defines a dance member class, this member class will overrides the dance defined in the object. Then,
dancing_team.dance(aDancingScript);will create an activity of dance defined by team.
To unify object references into the concept of object has three major benefits: