Index
Language is the dress of thought. -- Samuel Johnson (1709 ~ 1784)
Language is a very expensive dress that we cannot afford a change. Unfortunately, our thought often outgrows our language so fast that we always need a new language, which might cost us a life-time learning. If we ever need a new language, we should pick up the one that can spread as our thought grows, and can transform as our thought changes.
Unfortunately, existing programming languages usually provide a built-in model for a very limited program domain. For example, Ada's task with rendezvous entries, Java's thread and synchronized methods, Actor's active object model with communication port, SmallTalk's and Erlang's process are all used for concurrent programming. Unfortunately, the requirements of applications -- from multi-tasking within a single workstation to parallel processing in a distributed environment -- may vary greatly. The built-in model is a rigid dress that can never fit well to each requirement in our thought. The framework provided by the language should not be limited to a rigid model. It should provide a flexible framework that can be transformed into various high-level models for different application domains such as concurrent programming, distributed programming, internet programming, and parallel programming.
The diversity of the framework should not make the language a heavy dress. We cannot equip a language with miscellaneous built-in features for all possible high-level computing models, making a super hodge-podge language -- a dress so heavy that would scare people away.
Instead of providing domain specific computing models exhaustively, we should provide a transformable abstraction vehicle that can be adapted into various high-level computing models for diverse application domains.
Such diversity is obtained by unification. As shown in the following figure, a unification of two concepts A and B should result a concept that covers a wider range.
![]()
Unification not only provides a wider coverage, but also simplifies the concept as well as the language implementation. The fixed and built-in part of the language framework becomes smaller while the user-definable part becomes larger so that the frameworks are flexible enough to build various high-level application models.
There are four major unifications in Transframe:
- The object reference concept is unified into the class concept to provide an integration of various object reference protocols including the standard variables, constants, smart references, low-level pointers, as well as various user definable reference protocols such as network references, persistent references, and iterative references models. This unification has been discussed in my May column: Access to Objects.
- Generic class (C++ templates) are unified into the class concept to achieve the power of dynamic typing in a static system and enable more safe and reusable software component constructions. I will introduce Transframe's dynamic parametrism in my future column.
- Modules or packages are unified into the class concept to enable a more flexible software composition, which will also be introduced in my future column.
- The function concept is unified into the class concept; so that various computation models such as threads, processes, distributed servers, and communication models can be built in a high-level and well-defined architecture. In this column, I'll focus my discussion on function unification, which serves as an example to show the beauty and the power of the unification.
A class describes a set of objects in terms of
- their structure,
- their functionality,
- instantiation and termination processes,
- interfaces for object instantiations
The object structure is a geometric framework used to hold a set of component objects. This geometric framework is described by a number of local name declarations (for a description of name declaration, refer to my column: Access to Objects) within the class body. Each name refers to an member object or a list of member objects. Objects referred by these names are declared member objects. The object providing the framework to hold these member objects is the enclosing object. A declared member object can be visited by giving its enclosing object as well as its associated name. They may or may not be created automatically at the same time when their enclosing object is created, depending on the type of the name associated with the member object (refer to Access to Objects for detail). When the member object is not created, its associated name refers to a null object.
The object functionality is the capability of an object to spawn its member objects at run-time. This capability is described by a set local class declarations within the class body. These classes are member classes. The class that declares these member classes is the enclosing class of the declared member classes. To create an object of a member class, we must create it as a member of the enclosing object who is of the type of the enclosing class. Therefore, we should always provide an enclosing object to create an object of a member class. The object created through a member class is a spawned member object. They must be instantiated explicitly by a member class after the enclosing object is created.
Individual objects described by a class are called its instances. The process to create an object through its class is described by a constructor. This process is called object instantiation. A constructor gives an object instantiation interface and an execution body for object instantiations. The interface is specified by an interface type. An interface has a list of input specifications and/or an output specification. Input specifications describe the types of input objects which are necessary to be used to construct the new object. The description is given in terms of a number of list member declarations, often referred as formal parameter declarations. Output specification describes the type of the object which is produced as a result of the object instantiation. The execution body has a sequence of statements which describes the actions to be performed by the object instantiation.
A class may provide multiple constructors with different interfaces on inputs. The actual constructor used shall be the one with the interface that most closely matches to the types of the actual objects given for inputs. The actual given objects for inputs are actual parameters.
The process to kill an object is to release all the resources occupied by this object and clear of all its member objects. This process is called object termination. In general, the enclosing object should not terminate until all of its member objects are ready to terminate. Object termination may notify other objects that are using this object or the resource created by this object before the actual termination takes effect. This termination is called graceful termination. A class may have a destructor to describe a specific way for an object termination. In general, with the aid of leek-free resource manager, programmers are not required to write a destructor for an object. However, in particular cases, especially in low-level programming, users may be required to write destructors. Example is the implementation of leek-free resource manager itself. For example, to develop a smart object reference model, the model developer must write a destructor for the reference object itself to handle garbage collection. Users of the smart reference are then not required to explicitly free the object pointed by the reference.
The following example presents a typical class declaration, which is close to traditional static programming languages in syntax, however, we'll see a much richer semantics behind the familiar syntax as we explore the class concept later.
![]()
There are two kinds of object instantiations. Given a class C, a structural instantiation shall create an object of class C by executing the body of a constructor with a number of given actual parameters, and when the instantiation is done, the object of class C is returned. A functional instantiation shall create an object of class C by executing the body of a constructor with a number of given actual parameters, and when the instantiation is done, the object of class C is deleted and a new object (if any) of the type specified by the output is returned. Structural instantiations create objects that remain active until they are deleted explicitly or they are no longer used. They are used for creating objects that will last to interacts with the rest of the system, for examples, a file server, an object container, and an information collector. Functional instantiations are used to create objects that performs some functionality described by the constructor only. When they finished their performance, their duties are done and they are deleted. Examples of functional instantiations are a procedure to open a file, a process to display an image, a thread to deal with an event, a scientific calculation, and an activity to generate a signal.
Classes use structural instantiations as default object instantiations are structural classes. Classes use functional instantiations as default object instantiations are functional classes, or simply, functions. Objects created by a structural class are structural objects. Objects created by a functional class are functional objects. Structural object instantiations are often called object creations. Functional object instantiations are often called function calls.
The constructor for a structural class should not have an output specification. By default, its output is always the class itself.
A functional class may or may not have an output specification. Without an output specification, functional instantiation will produce a void output.
If a functional class is member class, we usually call it a member function. Member functions are commonly used for the enclosing object to communicate with the rest of the system. A member function call from the rest of the system shall create a functional object within the enclosing object, performing the required operations to update the state of the enclosing object, and/or generate an output.
Note that the concept of member class unifies the concepts of nested classes and member functions in a language like C++. By Transframe, functions are classes. A C++ member function call is equivalent to create a function object (a spawned member object) within the enclosing object to perform the given activities; and after the member object finishes its duty, it is deleted, and an output object (if any) is returned as result. The semantics of Transframe's spawned member object creation is much richer than C++ member function call, however. The protocol of object creation is user-definable, for example, user can define a thread creation within a task object. On the other hand, member class provides a truly nested class; while C++'s nested classes is only an ad hoc feature. Features like slot provided by dynamic programming languages can be implemented as a member class.
There are various ways to specify a Transframe class. Figure 2-2 are two typical examples. The example at the top is a simple way to define a class, which is often used to describe a functional class with a single constructor (interface). The example at the bottom is identical to a complete class declaration where foo is a subclass of function. The complete form is often used when a class (either structural or functional) has:
- object structure declaration
- member class declaration
- multiple constructor interfaces and execution bodies, or
- a destructor
When the concept of function is integrated into class, the traditional concept of a function call is generalized to object instantiation. The semantics of object instantiation is user-definable; so that users can create various well-structured high-level computational models such as multiple thread task, active object with communication ports, distributed server with remote services, etc. These models provide various frameworks enabling developers to write their applications in their own native expressions as if they were using their own domain-specific language as shown in the following examples.
class Account is AtomicData { transaction deposit(int) {...}; transaction withdraw(int) {...}; }; class NameSever is DistributedServer { remoteService register(...) {...}; remoteService lookup(...) {...}; }; class Pharmacy is Task { entry drop_prescription(...) {...}; entry take_medicine(...) {...}; }; class FaxMachine is ActiveObject { port receive (...) {...}; port send (...) {...}; };In the first example, transaction is a member class defined in Account's superclass AtomicData, which provides an all-or-nothing data transaction protocol. In Account, deposit is defined as a member class whose superclass is transaction. Calling deposit in an account object:
myAccount: Account(); myAccount.deposit(500);will create a deposit transaction in the account object, which either deposit 500 dollars successfully, or nothing happen as if the transaction were never created. The above presented structures are almost identical to traditional classes defined in C++, except member functions uses different call conventions (precisely, member object instantiation protocol) rather than a sequential function call.
When a class A is declared within the class body of another class B, A is an direct member class of B, and B is a direct enclosing class of A.
Class A is an enclosing class of B (or class B is a member class of A) if A is a direct enclosing class of B; or there is a class C such that A is a direct enclosing class of C and C is an enclosing class of B.
The class without an enclosing class is a free class.
When a member class A is specified as a subclass of D by inheritance, D must be a free class or a direct member class of C where C is a superclass of D's direct enclosing class. This rule is illustrated in the following figure.
![]()
This pattern is often used by high-level model design as shown in the figure below.
![]()
A member class belong to object scope. To use the member class to instantiate an object, we must provide an object of its direct enclosing class, for example:
class BallFactory is task { thread ball_producer (speed: integer); thread ball_consumer (speed: integer); }Threads ball_producer and ball_consumer are member classes. To create a thread in the task BallFactory, we must provide an object of BallFactory, which is the direct enclosing class of the thread class. Therefore, we have:factory: BallFactory; factory.ball_producer(1); // create a thread to produce 1 ball per second. factory.ball_consumer(2); // create a thread to consume 2 balls per second. factory.ball_producer(1); // create another thread to produce 1 ball per second
Given a class C, and a list of input objects X, object instantiation is a meta function that applies C's construtor to X and create a new object Y as defined in the constructor's interface. The list X is called the input list; and the object Y is called the output object. I'll explain the concept of meta class/function in my future column: Meta Programming.
A class C may have multiple interfaces, each interface must be associated with a constructor body if C is an concrete class. The input list must be convertible to the input type specified by one of C's interfaces. If so, we say the the input list matches the interface. If there are multiple interfaces that an input list can match, the interface has the closest match is selected.
Transframe has two built-in object instantiations: structural instantiation and functional instantiation.
Given a class C, a structural instantiation creates an object of the class C, and returns the object as the output; while a functional instantiation creates an object of the class C, and returns another object as the output of the class T, which is the output type specified in the applied interface, or has no output if the interface does not specify an output type. When the output is returned, the object of C is destroyed.
If C is an member class, the object must be created as a component object enclosed by an enclosing object, which an instance of the class that encloses C, or the subclass of the class that encloses C.
By default, a subclass of functional use the built-in functional instantiation, which is used to create objects that performs some functionality described by the constructor only. When they finished their performance, their duties are done and they are deleted. The built-in functional instantiation is equivalent to a sequential C++ function or member function call ( as a matter of fact, it is translated into a C function call in implementation.)
A class which is not subclass of functional uses the built-in structural instantiation, which is used to create objects that remain active until it is deleted explicitly or it is no longer used. This is equivalent to a C++ object instantiation.
The instantiation operator is the meta function used for object instantiation. The name of the operator is create.
Each class has an operator create, which is defined implicitly and uses one of the two built-in object instantiations.
If specific creation protocol is required, user must redefine the create operator in the following format:
when the class is a free structural class, use:
meta function operator create (input: selfclass.inputType): selfclass;when the class is a member structural class defined in class C, use:
meta function operator create (input: selfclass.inputType; encloser: C): selfclass;when the class is a free functional class defined in class C, use one of:
meta function operator create (input: selfclass.inputType); meta function operator create (input: selfclass.inputType): selfclass.outputType;when the class is a member functional class defined in class C, use one of:
meta function operator create (input: selfclass.inputType; encloser: C); meta function operator create (input: selfclass.inputType; encloser: C): selfclass.outputType;Given a C, let X be the input list, the following expression:
C Ximplies a call of the application operator:C create Xand:y.C Ximplies a call of the application operator:C create (y, X)When the output type is not given by the user-defined functional instantiation, the function call using the operator will generate void output even the function defines a non-void output in its interface.
If the operator create is not defined explicitly in C or C's superclass, the semantics of the object instantiation is interpreted by Transframe as either a structural instantiation or a functional instantiation, depending on whether C is a subclass of functional.
Consider a member function:
class Printer { function print (object_stream: ...); };and the function call:aPrinter.print ("integer is ", 23, "; float is " 23.0, ".");applies the class print to a list of the type (char[],integer,char[], float), which is equivalent to:print create (aPrinter, ("integer is ", 23, "; float is " 23.0, "."));It uses the built-in functional object instantiation.When specific object instantiation protocol is required, operator create should be defined explicitly, which overrides the built-in object instantiation, and any subclass will by default call the operator for its object instantiation.
Let process be a subclass of the class functional which overrides the operator create to replace the built-in function instantiation with a concurrent activity creation:
1 class process is functional 2 { 3 pcb: ProcessControlBlock; 4 function terminate() 5 { 6 // terminate the process by releasing all the resources 7 // occupied by the process 8 // Notify the process scheduler to kill the process 9 } 10 meta function create (input: selfclass.inputType) 11 { 12 // Create a blank process object with control block and stack 13 // initialized; 14 // Pushing the process object into the stack (will be used by the 15 // terminate method; 16 // Pushing "args" into the stack; 17 // Get the entry point of selfclass; 18 // Set the terminating method as the return address when 19 // the process is finished; 20 // Notify the process scheduler to register/start the process; 21 } 22 }; 23 24 process print (file: char[]) 25 { // open the file 26 // prepare the format 27 // and send it to printer to print 28 } 29 30 print ("transframe.doc"); 31 print ("tf_logo.gif");The object instantiation using the class print at line 30 is a process creation. Since the creation operator is overridden in print's superclass process, the overriding operator is called, which creates a process object registered in a process scheduler. The scheduler puts the process into a ready-to-run queue and re-schedule the current running process. And the control may return to line 31, to create another process.
When the process created at line 30 is terminated, the method terminate is called, and the process is killed. Then, process scheduler re-schedules the current running process.
Note that in this example, calling a process never return an output since the output is not declared in the create operator. Should output be required, the output of a concurrent execution unit must be handles carefully. It must be wrapped in a specific object reference that can support a proper synchronizing mechanism.
Class parameters can be used to define the priority or other process properties that must be presented by a concrete process. For example:
class process #(priority: integer) is functional {...}; process #(LOW_PRIORITY) print (file: char[]) {...};
Let thread be a member class of task. A task encapsulated a set of resources in which a number of threads can share the resources concurrently. Function call to a thread will create a thread object within the task.
1 class task 2 { 3 // member declarations for thread instance management here; 4 class thread is behavioral 5 { // member declaration for a thread instance here 6 function terminate() 7 { 8 // terminating; releasing occupied resource 9 // Notify task::self to kill the thread 10 } 11 meta function create (encloser:task; input: selfclass.inputType) 12 { 13 // Create a thread object 14 // Pushing the object into the stack; 15 // Pushing "args" into the stack; 16 // Get the entry point of selfclass; 17 // Set the terminating address; 18 // Notify task::self to register/schedule the thread 19 // (task::self is the first object in input.) 20 } 21 } 22 exit () 23 { 24 // task exit, kill all the thread within the task 25 // or wait until all threads are finished 26 } 27 }; 28 29 class SelfSupplyingAutonomyAt line 42, a family bakery is created as a self-supplying cake maker and consumer. When the class producer is applied to the input 5, the operator create defined in the thread class is called. It creates a thread object within an object of the enclosing class, namely, family_bakery. Therefore, at line 43, a thread of cake maker is created within the family bakery task. It produces cakes at rate of 5 per minute. Afterwards, a consumer thread is created to eat the cake at the rate of 10 cakes per minute, and so on.is task 30 { 31 storage: pool of ProductType; 32 thread producer (speed: integer) 33 { 34 // threads generating products at the rate given by speed 35 }; 36 thread consumer (speed: integer) 37 { 38 // threads consuming products at the rate given by speed 39 }; 40 } 41 42 family_bakery: SelfSupplyingAutonomy#(Cake)(); 43 family_bakery.producer(5); 44 family_bakery.consumer(10); 45 family_bakery.producer(5);
Language is called the garment of thought: however, it should rather be, language is the flesh-garment, the body, of thought. -- Thomas Carlye (1795 ~ 1881)
Indeed, a language should shape the concept of our ideas without a distortion of the body of our thought. Fitting a "circular" concept in a "square" not only requires the square dress larger and heavier, but also, the worse, twists out of the true meaning of the concept. A programming language should provide frameworks to fit our application software ideas, rather than warp applications to fit the language framework. In his Turing Award Lecture Paper, John Backu's critic on rigid frameworks that need to be big in order to suit users still holds true for many current languages.
Transframe's unification enables a flexible framework for various applications. It provides a beautiful dress that shapes software concepts faithfully. By unification, the fixed and built-in part (the hard part) of the language framework becomes smaller while the user-definable part (the soft part) becomes larger so that the framework are flexible enough to build various high-level application models.
Built around Transframe, the environment provides various high-level computing models in the form of application frameworks. Unlike existing application frameworks wrapped in an unfitting box, for example, Taligent frameworks in C++ templates, Transframe application frameworks are represented in their fitting architectures. Application developers will always use their own native model to describe what they are going to develop as if they were using their own domain specific language.
Transframe, as its name implies, is a language for various transformable frameworks.