Transframe

A Multi-Paradigm Programming Language

Introduction

Language shapes the way we think, and determines what we can think about.”

- Benjamin Whorf.

A programming language is the dress of our software concepts. Fitting a "circular" concept in a "square" dress not only requires the dress larger and heavier, but also, the worse, twists out of the true meaning of the concept. We want a language to provide frameworks shaping our application ideas, rather than twisting our applications to fit the frame of the language.

On the other hand, the fast expanding software business has introduced many new dimensions of applications, which sets up more and more requirements on specific language features to support things like dynamic genericity, database inquiry, distributed computing, parallel processing and transactional operation. Obviously, we cannot simply put all the stuff into a language to make it a super hodge-podge.

Transframe is a general-purposed object-oriented programming language. Like Java and C#, Transframe is also a superficially C/C++ alike language, but is designed rather differently. Language design is always affected by compromise and trade-offs to some extent. But a language is not just a syntactic combination of all the concepts in isolation. Keeping adding language features can only result a hybrid language that may favor one application domain but may disfavor the other. Therefore, Transframe is not a result from addition and subtraction; rather, it is a work of unification and simplification based on C and C++. It emphasizes on simplicity, understandability, programming productivity and code efficiency.

The most important functionality of a language is communication. The only means to write understandable software packages is to write them as if they were presented in a native language. Transframe's capability to build up various domain-specific frameworks significantly simplifies the application developer's work. Unlike existing application frameworks wrapped in an unfitting box, for examples, STL containers in C++ templates, database query in C-style function calls, and distributed and parallel computing models in any language that does not have a high level language feature support. Transframe application frameworks are presented in their fitting architecture. Application developers will always use their own native model to describe what they develop as if they were using their own domain specific language.

Instead of introducing built-in language features or syntax sugar, Transframe provides domain-specific features by a series of concept simplification and unification so the language’s rigid built-in core is reduced to minimum. Existing programming languages usually provide a built-in model for a limited application domain. Transframe’s diversity is obtained from simplifying the fixed and built-in part of the language feature; so that the user-definable part becomes larger and the features are flexible enough to build various high-level models for applications. Instead of bringing about brand new concepts, Transframe provides a natural extension to the ordinary concept of class that is familiar to most programmers.

Unlike Java and C#, Transframe eliminates unsafe defects in C by upgrading them into a safe unified concept, rather than simply excluding them. Examples are pointers and variable function inputs. Transframe enables useful concepts in other languages by integrating them into an existing unified concept, rather than simply adding them into the language to get yet another hybrid language. Examples are generic classes, delegates, lambda functions, database query interfaces and smart references. The language is simplified by reducing the number of hybrid concepts and by eliminating ad hoc language features.

Major innovations of Transframe include the unification of generic classes and classes into the same notion; the union of functions into the class concept; the generalization of the notion of declaration; and the integration of primitive types into class hierarchy without sacrificing code efficiency. These innovations make Transframe a pure and consistent programming language, capable of providing multiple paradigms with minimum syntax core.

In Transframe, a generic class is a class. This uniformed concept not only simplified the concept of generic class, but also enables dynamic polymorphism based on genericity. C++ templates are not classes because they cannot be used in a way that a class is used. C#’s generic class, although provides dynamic binding for object classes, is still not a real class because it cannot be used in declaration unless all class parameters are bound - the same restriction imposed by C++ templates. Transframe’s generic class can be used in declaration for polymorphic variables with unbound class parameters.  Dynamic class parameter binding can be used for primitive classes (types) without Java’s expensive boxing.

The concept of function is integrated into the notion of class; so that the traditional concept of a function call is generalized to object instantiation (creating an activity object). Member functions are member (nested) classes. The semantics of object instantiation is user-definable; so that various specific function call protocols can be specified by the language user, for example, thread creation, distributed server with remote procedures, reactors with event handlers, pipelines, and parallel computations. The unified concept also eliminated the need to create complicated features such as C#’s Delegation and C++ Function Adapter when functions are treated as types or values (first-class objects).

Variables, pointers, and smart references are objects of referential classes, which are a generalized concept for accessing object and have a broader meaning for various user-defined object reference protocols such as atomic object references, operating system resources, and remote references. A declaration bounds a name to an object (or value). The name itself is an instance of a referential class that defines how the object referred by the name is stored and accessed. Built-in reference semantics in other languages cannot satisfy various application requirements. Transframe’s referential class enables users to develop not only a safe memory management, but also a safe use of system resources, as well as specific protocols such as transactional memory and remote references in network spaces.

Primitive types are integrated into class hierarchy. This orthogonality enables objects of primitive types to answer the who-are-you question in a polymorphic situation, so that primitive types are not necessarily to be treated differently than other types. This achieves a simpler but more flexible type system. There is no need to create a parallel class hierarchy for boxing primitive types as in Java in situations where polymorphism is required. There is also no need to treat primitive types differently than other classes when they are used to bind class parameters as in C#’s generic classes.

Transframe is designed to be type-safe. It improves the safety by upgrading the unsafe features into a unified concept. Pointers are integrated into the object reference concept which also includes smart references as well as other user-defined references. Variable number of function inputs are treated as a heterogeneous object stream where type information are preserved. Union types are upgraded to a concept of super class construction. Array is presented by an abstract generic class which is type safe and efficient. Transframe's type system supports a more precise interface specification (type dependency among input and output parameters), which diminishes many run-time type checks that would otherwise be required in C++, C#  and Java. A typical example of this is to check the element type of a container.

Generic Programming

Transframe unifies the concept of generic class into the concept of class. A generic class has no difference from a non-parameterized class in language concept. Unlike traditional approaches in C++ and C#, Transframe's parameterized class is a real class. Let us consider an example:

   class Operator < VehicleType: type of Vehicle >
   {
      func operate(VehicleType);
   };

Or,

   class Operator < VehicleType: #Vehicle >
   {
      func operate(VehicleType);
   };

where #Vehicle is a brief notation of type of Vehicle , or type<Vehicle>.

The class Operator has a class parameter VehicleType, which can be bound to any subtype of Vehicle. Since C++ template or C#’s generic class is not a real class by language concept, you cannot use them to declare polymorphic variables without binding of the class parameter to a concrete type. The equivalent declaration:

   x: Operator;  // Transframe uses Pascal-alike declaration syntax, we will discuss the reason later.

is not allowed in C++ or C#, but is a valid declaration in Transframe. Transframe’s generic class enables a fully dynamic polymorphism implemented in the class hierarchy.

To define an operator list, the intuitive way is to use the class Operator to constrain the class parameter:

   class OperatorList < OperatorType: #Operator >
   {
   };

However, this cannot be done in C# because Operator is not a real class and cannot be used in class parameter constraints. The following C# code:

   class OperatorList < OperatorType > where OperatorType : Operator
   {
   };

is invalid. To overcome this, you have to introduce another class parameter:

   class OperatorList < OperatorType, VehicleType > where OperatorType : Operator < VehicleType >
   {
   };

This approach makes code unnecessarily complicated – just imagine that you have to include class parameters in all levels as your composite structure builds up in progress:

   class YourCompositeClass < T1, T2, T3 … > where T1: C1 <T2>; T2: C2 <T3>; T3 …
   {
   };

In the end, no one will understand your composite class.

It is quite often in real-world applications to use mutual parameterization, for instance, the Vehicle is a generic class depending on its operator:

   class Vehicle < OperatorType: #Operator >
   {
      func assign (OperatorType, Vehicle);
   };

If a generic class is not a real class, such mutual parameterization is impossible. A practical example of using mutual parameterization  is the Container class and its iterator type:

   interface class Container is Comparable;
   interface class Iterator <CT: #Container> is SharedRef;
   interface class Container < ET: #Any; IT: #Iterator <CT=selfclass> > is Comparable
   {
   };
   interface class Iterator <CT: #Container> is SharedRef < T = CT.ET >;
   {
   };

Iterator is a subclass of a SharedRef (a subclass of referential class), which defines an iterator object to traverse through elements of a container. The type of the objects pointed by the iterator is the element type of the iterator's associated container. In the container's iterator type, the container type is bound to selfclass. Without the support of this mutual parameterization, it will be difficult to provide a safe iteration in which iterators can use the container's type information to check their boundary and limit.

It is also quite often in real-world applications to maintain a heterogeneous list, for instance, a list of operators that drives different vehicles, for instance,

   any_operators: OperatorList;

The above declaration is impossible if a generic class is not a real class.

Transframe lets you easily decide the degree of the heterogeneity of a container’s element:

   pilots_1: OperatorList < Operator<Airplane> >;
   pilots_2: OperatorList < Operator<VehicleType: #Airplane> >;
   pilots_3: OperatorList < Operator<Airbus> >;
   pilots_4: OperatorList < Operator<Boeing> >;

If an airplane operator can operate any airplane, you can have pilots_1 without any problem. In pilots_1, the VehicalType is bound to Airplane. If the real-world application is not such a case, you can have pilots_2, where the VehicalType is not bound yet, but re-constrained to the type of Airplane. And then you can have pilots_3 and pilots_4 for concrete pilot lists. We will discuss the Transframe subclass rules later and by then, you will understand why you can assign pilots_3 to pilots_2, but not to pilots_1.

The notion:

   Operator <Airplane>;

is equivalent to:

   Operator <VehicleType = Airplane>;

where the class parameter is bound to the class Airplane. Transframe also provide a brief notion for unbound class parameter. The notion:

   Operator of Airplane;

is equivalent to:

   Operator <VehicleType: #Airplane>;

where the class parameter is re-constrained to the class Airplane. And class parameter can be bound to or re-constrained by any subclass of Airplane in its subclasses. To re-constrain multiple parameters using the of notion, a pair of parentheses is required.

By Transframe’s subtype rule, Operator <Airplane> is a subclass of Operator of Airplane, but not vice versa.

selfclass

The notion of selfclass is used to denote special type when used in class.

selfclass is equivalent to the type of self. It is an implicit class parameter that is not bound until its enclosing class is exact (an exact class cannot have any further subclasses.) It is automatically re-constrained by the enclosing class in subclasses. Consider:

   class LinkNode <T: #Any>
   {
      V:    T;
      next: selfclass;
   }

In declaration:

   p: LinkNode <int>;

The type of p.next , i.e. the selfclass, is bound to LinkNode <int>. Because LinkNode <int> is an exact type.  If we have a variable:

   P1: LinkNode of Numeric;

The type of p1.next is undetermined. The selfclass is re-constrained by LinkNode of Number, or, LinkNode <T: #Numeric>. What guaranteed is that the type of p1.next is the same type of p1.

Type Safety

Programmers view genericity as an alternative means for reuse and polymorphism. But there is an important usage that cannot be substituted by features of inheritance and polymorphic variables. It is the specification of type dependency. Consider:

   class Vehicle;
   class Limo : public Vehicle;
   class Airplane : public Vehicle;
   class Operator { deferred func operate(Vehicle); };
   class Chauffeur : public Operator { func operate(Limo); };
   class Pilot : public Operator { func operate(Airplane); };

The operate function interface defined in the superclass Operator fails to reflect the fact that a concrete operator cannot drive any type of vehicle. That is, the type of the vehicle that an operator can drive depends on the type of the operator. This imprecision causes subclasses of Operator unable to inherit the interface and they have to correct the interface by covariant redefinition of the input, which will generate a loophole in type safety.

An experienced C++/C# programmer would tell you that parameterized classes should be used for this kind of dependency:

   class Operator < VehicleType: #Vehicle >
   { 
     deferred func operate(VehicleType);
   };
   class Chauffeur: public Operator<Limo>;
   class Pilot: public Operator<Airplane>;

The operate function interface is redefined in classes Chauffeur and Pilot via the class parameter VehicleType.  At this point, however, we only specified a shallow type dependency. Consider:

   class OperatorList < OperatorType: #Operator >
   {
      func assign (op: OperatorType; v: Vehicle);
   };

The function interface of assign is not type safe. Obviously, the vehicle assigned to the operator must be the vehicle type that the operator can drive, but the interface failed to declare such type dependency. In Transframe, you can simply describe this deep type dependency by:

      func assign (op: OperatorType; v: OperatorType.VehicleType);

A class parameter in Transframe is a member attribute of a class. Just like a data member of an object, you can access the class parameter through any name that refers to the class. OperatorType is a class parameter of OperatorList, which is a name that refers to a subclass of Vehicle. You can access its class parameter VehicleType through the expression OperatorType.VehicleType. Without this convenience, you will need to introduce a redundant class parameter, VehicleType, that unnecessarily complicates your design and makes your code harder to understand, as in C#:

   class OperatorList < OperatorType, VehicleType > where OperatorType : Operator < VehicleType >
   {
      void assign (OperatorType op, VehicleType v);
   };

The above method will make code very complicated when the type dependency get deeper, or a generic class has more than one dependent class parameter.

Declaration and the Beyond

Declaration - A Superficial Tour

A declaration bounds a name to an object or value.

   x:int;

   y:int = 3;

   z:=3;

Each of the above three declarations declares a variable name which bounds an object of int (an integer value). The second one has an initial value 3. The type in the third declaration is implicitly inferred by the initial value. 

A declaration can have multiple names. For instance,

   n1,n2:=2;

both n1 and n2 gets an initial value of 2 and the type of int. The following declaration

   n1,n2 :=2,3;

declares two names, n1 and n2, which is assigned to 2 and 3 respectively. In the following declaration:

   n1,n2,n3:=2,3;

n3 gets the last value in the initialization list, i.e., the value of 3 in type of int. Consider:

   n1, n2 := (2, 3);

n1 and n2 both get a tuple value (2,3) and the type (int,int). In the following declarations:

   n:= (2,”string”);

   n1, n2:= *n;

The operator * is used to decompose the container. n1 gets a value of 2 and n2 gets a value of “string”. If elements contained in the container are not enough, the subsequent names will get a value of the last element in the container. If the container is empty, all names get a value of null in type of any. For instance:

   n1,n2,n3,n4 := *(2,3);

n3 and n4 will get a value of 3 in type of int. And in the following declaration,

   n1,n2,n3:= *();

all names get a value of null in type of any. Here are more declaration examples:

   n1,n2: int;

declares two integer names with initial value of null (a null integer is an undefined value, not zero.) The following declaration

   n1,n2:=null;

has two names in any type with a null initial value. They can be assigned to object or value in any type later. In

   n1,n2,n3,n4:=(2,”string”),*(3,’c’),4;

n1 is assigned to a tuple value (2,”string”); n2, to an integer 3; n3 to a character ‘c’; and n4 to an integer 4.

In-Depth Understanding of Declaration

The concept of name in Transframe is unified into the concept of object. 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:

1       x1: Image;
2       x2: static <Image>;
4       x3: URL <Image>;
5       x4: Persistent <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 C# and Java; x2 is equivalent to a variable in languages like C++; 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 a persistent object storage.

All names declared by declarations are instances of a subclass of Referential, which is an abstract interface class parameterized by the type of the object that can be associated with the name:

   interface value class Referential <exact T: #Any>;

where T is the class parameter that denotes the actual type of the object represented by the name.  The keyword exact used for the class parameter prevents the class parameter from being bound to an abstract class.

Transframe has four built-in referential classes: type, static, dynamic, and pointer.

A static name is permanently bound to a 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 a static name cannot be changed. Static names not only refer to objects, but also provide storage to hold the object value. Therefore, two static names will always refer to different objects. Whether a name in declaration is static depends on whether the class used in declaration is a value class or the static referential class is explicitly used. For examples:

   x:=3;

   y: (string, string[]);

   z: static <string[5]>;   // or z: static of string[5]

The first two declarations declare a static name by default because int and tuple are value classes. The third one must explicitly give the static referential class because array is not a value class. The type used to declare a static name must have a fixed size, i.e., the size of its object storage must be static, or in other words, must be determined at compile time. For instance,

   z: static <string[]>

is not a valid declaration because the array of string has a dynamic size. A class has a fixed static size if it is

·         a concrete class, or

·         an abstract class with a fixed keyword.

If a class has a fixed keyword, you cannot add new member object declarations in its subclasses but you can still extend the class by introducing or overriding its member classes (functions) in subclasses.

Please note that referential call itself is a value class. Therefore,  a reference itself is contained in a static name by default.

Shared references point to dynamically allocated pieces of storage to contain the object declared, and the location of the storage can be shared by different dynamic names during the life-time of the object.

Dynamic References is a Shared Reference that provide automatic memory reclamation, enabling a memory-leakage free programming environment.

   x: =”my string”;

   y: MyWidget;

   z: dynamic <int>; // or z: dynamic of int;

Type names are shared references representing types (classes):

   x:= List<int>;

   y: type <List>;  // or, y: type of List

   z: #List;         // the same as type of List. # is a brief notation.

   class MyWidget: Widget;  // declare a subclass of Widget

   Widegt MyWidegt;         // the same as above

Please note that the last type name declaration has the format "Superclass SubclasName". It is similar to C++'s declaration in the form "Type Name". In Transframe, this form of declaration is used only for type declaration, for instance:

   func print (args:Any...);

where func is a Functional class and the above declaration introduces a subclass of func, print, which specifies a printing function.

Pointers are shared references and are bare low-level memory addresses that does not guarantee free of dangling objects or invalid references. They are equivalent to memory pointers in languages like C++ and C and will only be used in system-level programming, especially in the implementation of memory management for user defined referential classes and containers. They can also be used in an opened object scope (by with statement) as a temporary and efficient access path to the object. Unlike C++, however, you do not use notation “*” and “->” for pointer arithmetic. They are used the same way in all referential classes. For example:

   x: pointer of Widget;         // or x: *Widget;

   x = new TextInput;            // create a pointer to TextInput (a subclass of Widget) object and assign it to x;

   x.setText(“Message Dialog”);  // call member function of the widget

The notion “.” can also be used to access the member of the reference itself:

   x.addr();

where addr is a member of the pointer reference. In case of a conflict, the member name of the object referred by the reference takes the priority and “&” must be used to access the member of the reference itself:

   &x.addr();

This conflict case should not happen because most of the member functions of a built-in reference are implicit member functions that cannot be called directly and will not be obscured by member functions defined in the object scope.

While the expression “x” denotes the object referred by the reference, the expression “&x” denotes the reference itself. Consider,

   x: pointer <int> = new int(3);

   y: int = 4;

In the following assignment:

   x = y;

the integer contained in y is copied into the memory referred by x, because x and y are difference reference classes. However, the following assignment:

   x = &y;

is equivalent to:

   x = &y.addr();

It makes x directly point to the memory of the static reference y.

Transframe provides the following brief notions for its four built-in referential types:

   #T                  type of T, or type <T> if T is an exact type

   @T                  static of T, or static <T> if T is an exact type

   *T                  pointer of T, or pointer <T> if T is an exact type

   ^T                  dynamic of T, or dynamic <T> if T is an exact type

Please note, for a non-value class T, the declaration

   x: T;

and

   x: ^T;

are the same, because the default reference used for non-value class is dynamic reference. However, for a value class T, the declaration

   x: int;    //  &x is static <int>

and

   x: ^int;  //  &x is dynamic <int>

are different, because int is a value class. For a value class, the following declaration:

   x1: int =3;

and

   x2: @int = x1;

are the same. When x1 is assigned to x2, x2 holds a distinct copy than x1. To make a static reference share the copy with other static reference, a declaration decorator ref is requited:

   ref x3: int = x1;

x3 shares the same copy held by x1. When ref x3 appears in a function input interface, it is an input parameter by reference. x3 can only be assigned by a static reference, not a R-value.

Object Instantiation and Reference Initialization

Object instantiation is the process to create an object with an associated object reference. It involves three parts: the referential class (not a type reference), the object type, and the inputs that match one of the constructors of the object type.

A constructor and destructor specified in a class, for instance,

   class Sphere: public Geometry3D

   {

      public:

         enter (radius: double);

         exit ( );

   }

are meta member functions:

   class Sphere: public Geometry3D

   {

      public meta:

         _enter (obj: address, radius: double);

         _exit (obj: address);

   };

A declaration:

   x: Sphere(0.5);

is actually a call to the meta member function:

   Sphere._enter(&x.alloc(Sphere.obj_size()), 0.5);

where alloc is an implicit function defined by the referential class:

   class dynamic: public SharedRef

   {

      public:

         implicit alloc (sz: size_t): address

         {

            count_ = new int(1);

            pointer_.set_addr(malloc(sz));

return pointer_.obj_addr();

         }

   };

Consider a static reference (note that vector is a sealed value class,)

   v: vector<int>;

the static reference &v is in the type of static< vector<int> >, we have:

   &v# == static< vector<int> > // the type of &v, i.e. the reference itself,  is static< vector<int> >

   v# == vector<int>  // the type of the object referred by v is vector<int>

   v#.ET == int  // the element type of the vector referred by v is int

x initially contains an empty vector by calling its object types default constructor, which is equivalent to:

   vector<int>._enter (&v.alloc(vector<int>.obj_size()));

The function alloc defined in static reference is interpreted by the compiler and it returns a memory address statically allocated at the compile time. When the  scope where the static reference is declared exits, the destructor of the object is called:

  vector<int>._exit(&v.obj_addr());

The result of the object instantiation always creates a reference that contains or points to the created object. When the name of the object reference is not given, the reference is created anonymously in the current scope as an R-value and will be destroyed automatically when the R-value pops away. However, the object contained or pointed by the reference may or may not be destroyed, depending on how the destructor of the reference is defined.

Assignment makes references containing or pointing to the assigned object. The expression at the left side (L-value) must evaluate to a reference. The right side expression can evaluate either to a reference or an R-value. Assignments have different semantics for Static Reference and Shared References.

Consider the static vector reference v we declared in the previous example. When we assign it to a new vector:

   v = vector<int>(2,4,6,4,7,8);

the right side evaluates to a R-value. From the implementation point of view, the R-value is a temporary static object value created in the stack of the current scope. The assignment uses an efficient memory move semantic to directly copy the memory value into the static reference and dispose the temporary memory in the stack. Here are the detailed steps in implementation:

   v.exit(); // Call the currently contained object (vector)’s destructor, if any.

   memcopy (&v.addr(), current_stack_top, v#.static_size()); // Move the memory of R-value to the memory of the static reference v.

      pop_stack (v#.static_size()); // Dispose R-vlaue without calling any destructor.

If the right side evaluates to a reference, for instance,

   y: vector<int>(2,4,6,4,7,8);

   v = y;

The assignment v=y will result a copy of the entire vector contents using the copy constructor, as interpreted in the following semantic:

   v.exit(); // Call the currently contained object (vector)’s destructor, if any.

   y#._enter (&v.addr(),y); // Call the object type’s copy constructor that accepts an input of the same type.

Assignment of a shared reference has a different semantics. Consider a dynamic reference:

   x: Sphere;

or

   x: dynamic<Sphere>;

The reference x is first initialized to a null reference by a default dynamic reference constructor. If the right side of the expression evaluates to a reference of the same class (dynamic in this case), for instance,

   y: Sphere (0.5);

   x = y;

the assignment make x to refer to the same object by the following semantic:

   &x.exit(); // Please note that the reference’s destructor, instead of the object’s destructor is called.

   &y#._enter (&x.addr(), y);  // Call the reference type’s copy constructor (not the object’s copy constructor) that accepts an input of the same type.

The destructor and the copy constructor of the dynamic reference is defined as following:

   sealed value class dynamic: public SharedRef

   {

      public:

         enter (other: selfclass)

         {

            count_ = other.count_;

            obj_addr_ = other.obj_addr_;

            if (obj_addr!=null) count++;

         }

         exit ()

         {

            If (--count==0) { delete obj_addr_; delete count_; }

         }

   };

If the right side of the assignment evaluates to an R-value of a dynamic reference compatible to the declared type x, for instance:

   x = Sphere (0.5);

or

   func create_sphere (r: double; loc: xyz_location): Sphere;

   x = create_sphere (0.5, (0,0,0));

The copy constructor will not be called. Instead, the efficient memory move semantic is applied. Here are the detailed implementation steps:

   &x.exit();// Call the reference’s destrcutor.

   memcopy (&x.addr(), current_stack_top, &x#.static_size()); // Move the memory of R-value to the memory of the dynamic reference x.

      pop_stack (&x#.static_size()); // Dispose R-vlaue without calling any destructor.

If the right side of the assignment evaluates to a reference of a different type, for instance, a pointer reference, the copy constructor will always be used, regardless whether it evaluates to an R-value or not. If the reference protocol does not provide the copy constructor that matches the provided reference type, the assignment type check will fail. For instance:

   x = new Sphere (0.5);

The right side evaluates to a reference of pointer<Sphere>, and the following copy constructor will be invoked:

   sealed value class dynamic: public SharedRef

   {

      public:

         enter (other: pointer<T>)

         {

            count_ = new int (1);

            obj_addr_ = other.obj_addr_();

         }

   };

Please note, using pointers is unsafe and the above example should only be used in system level programming.

The following assignment will fail:

   y: static<Sphere>(0.5);

   x = y;

if the dynamic reference does not have a copy constructor that accepts a static reference.

Referential Classes

The root  referential class is an interface class, defined partially as below (see the Standard Reference for the complete definition):

   interface value class Referential <exact T: #Any>

   {

   public:

      deferred implicit func open (): pointer <T>;

      deferred implicit func close (status: exception);

      deferred func obj_addr (): address;

      func obj_size (): { return T.static_size() }

      func addr (): address { /* returns the address of the reference itself. This is interpreted by compiler */ }

   };

The Referential class is a value class. Its instance is by default allocated in static memory. The class parameter T is exact, so you cannot bind T to an abstract class. It defines an opaque object reference protocol because you cannot access member objects or member classes (functions) through the reference. The only way to enter the object is to use a with statement, which opens the object scope so that the object’s public members can be accessed as if they were used within the object scope. Implicit functions open, close cannot be called directly. They are called implicitly to open and close the object scope in with statement. Example:

   x:=Widget();

   with (x)

   {

      setTitle(“message Dialog”);

      openWindow();

   }

When the scope of the with statement is entered, the open function of the referential class is called. In the example shown above, it calls the open function of the dynamic class, and returns a temporary memory reference used to access members of the widget object. At the point of exiting the with statement, the close function will be called to perform necessary closing operations, if any.

Accessible object references provide a function “.” through which public members can be accessed:

   interface value class AccessibleRef: public Referential

   {

   public:

      deferred implicit func . ():pointer <T>;

   };

The function “.” can only be called implicitly when it is associated with a class member name. For instance:

   ar:= dynamic <(x:int; y:int)> (2,3);

   z := tr.x + tr.y;

The expression ar.x is interpretered as: (1) call tr’s function “.” to get the address of its allocated object; and (2) use the obtained address to get the value through the member reference x. The second part of this process is interpreted by the language compiler.

Shared object references are transparent references that allow multiple references pointing to a shared object. SharedRef is defined as below:

   interface value class SharedRef: public AccessibleRef

   {

   };

Dynamic, Pointer, and Type are three built-in sealed subclasses of SharedRef.

Pointer reference is defined as below:

   sealed value class pointer: public SharedRef

   {

   public:

      implicit final func open (): pointer <T> { return self; }

      implicit final func close (status: exception) { }

      implicit func alloc (sz: size_t): address { obj_addr_= malloc(sz); return obj_addr; }

      implicit final func . ():pointer <T> { return self; }

      final func obj_addr (){ return obj_addr_; }

        final func set_addr (addr: address) { obj_addr_=address; }

        enter (): obj_addr_(null) { }

        enter (other: pointer<T>): obj_addr_(other.obj_addr_) { }

        enter (other: static<T>): { T._exit(obj_addr_); T._enter(alloc(T.static_size(),other); }

   private:

      obj_addr_: address;

   };

Dynamic reference is defined as below:

   sealed value class dynamic: public SharedRef

   {

   public:

      implicit final func open (): pointer <T> { return pointer_; }

      implicit final func close (status: exception) { }

      implicit func alloc (sz: size_t): address

   { count_=new int(1); pointer_.set_addr(malloc(sz)); return pointer_.obj_addr(); }

      implicit final func . (): pointer <T> { return pointer_; }

      final func obj_addr (){ return pointer_.obj_addr(); }

        enter (): pointer_(), count_() { }

      enter (other: pointer<T> ): pointer_(other), count_(other ? new int(1) : null) { }

      enter (other: dynamic <T> ): pointer_(other.pointer_),count_( other ? ++other.count_ : null) { }

      exit () { if (pointer_ && --count_ <= 0) { delete pointer_; delete count_; } }

   private:

      pointer_: pointer<T>;

      count_  : pointer<int>;

   };

The empty constructor makes a null reference, which has no concrete type if the reference is declared as a polymorphic reference. The other two constructors define how the reference is created from other reference.

An exclusive object reference are transparent reference that points a distinct object and cannot be shared by other references:

   interface class ExclusiveRef: public TransRef

   {

   public:

      deferred prefix func &( ) pointer <T>;

   };

static is a built-in subclass of ExclusiveRef.

   sealed value class static: public ExclusiveRef

   {

   public:

      implicit final func open (): pointer <T> { return pointer<T>(obj_addr()); }

      implicit final func close (status: exception) { }

      implicit func alloc (sz: size_t): address { return obj_addr(); }

      implicit final func . ():pointer <T> { return pointer<T>(obj_addr()); }

      final func obj_addr (): address { /* interpreted by compiler */ }

      final prefix func & ():pointer <T> { return pointer<T>(obj_addr()); }

   };

A static reference does not have any constructor. It is always used in associated with the object constructor.

Type reference is a special reference to refer to a type (class). The usage of the type reference is interpreted by the built-in language semantic. Each type (class) has a type code described _type_descriptor. A type reference constrained by the type T is implemented by a memory pointer to T's type descriptor: _type_descriptor_T, which is a subclass of  _type_descriptor. The type reference does not have a alloc function. Therefore, it cannot be used to construct a type. All type descriptors are created internally by the language compiler and/or the language run-time system. Here is the pseudo definition of the type reference

   sealed value class type: public TransRef <T: #_type_descriptor>

   {

   public:

      implicit final func open (): pointer <T> { return pointer<T>(obj_addr()); }

      implicit final func close (status: exception) { }

      implicit final func . ():pointer <T> { return pointer<T>(obj_addr()); }

      final func obj_addr (){ /* interpreted by compiler, reurns the address to the type descriptor */  }

   };

 

Atomic Programming – An Example of User Defined Reference

Atomic programming can be implemented by opaque object references and using with statements. The open function will create a memory copy so that all operations will be performed in the memory copy. At the end of with statement, the close function will commit the change, if any, to its original or permanent storage, or when exception is raised during the execution within the with statement scope, the close function may abort any temporary change to the object.

The following example defines an atomic reference that implements a persistent map:

   class persistent_map <IndexType: #Comparable; EntryType: #Any>

   {

      class atomic_entry: public ref <EntryType>

      {

      public:

         implicit func open (): const pointer of EntryType

         {

 // creates two internal memory copies: copy_ and update_

 // returns the pointer of update_ for write operation

         }

         implicit func close (const status: exception)

         {

            Lock();

            if ( status == normal )

            {

               if ( hasConflict (copy) ) retry();

               else commit (update);

            }

            else

            {

               abort();

            }

         }

      private:

         copy_: pointer of EntryType;

         update_: pointer of EntryType;

      };

      func [index:IndexType] (): atomic_entry;

   }

Using the above map, we can define a persistent map Accounts as following:

   class Account

   {

   public:

      balance: int;

      // …

      // other implementation of account entry

   }

   class Accounts: public persistent_map <int, Account>

   {

   public:

      func withdraw (id: int; amount: int)

      {

         with [id] do

         {

        if (balance > amount)

           balance -= amount;

         }

        }

   }

In the function withdraw, the with statement uses the function “[]” to get an atomic reference of the map entry with the given id. At the beginning of the with statement, value of the account will be loaded into memory, creating two copies, one for updating, and another for conflict checking. At the end of the with statement, the value in the memory copy will be compared with one in persistent storage, and the with statement will be retried if there is any conflict caused by another process that has modified the account during the execution of this with statement. The with statement will abort if the entry of the given id is not found by raising an exception.

Function and the Beyond

Function - It's not a Language Built-in Feature

Let’s start with a simple function:

   func print (object_list: [])
   {
      foreach (obj in object_list) print_obj(obj);
   }

You may not sense anything new in the above code. Under the syntax of a function declaration in Transframe, the semantic is extended: func is not a keyword. It is actually a class. print in the declaration is a subclass of func. You can write the same code in the following format:

   class print: public func

   {

        enter (object_list: [])     // constructor

        {  

            foreach (obj in object_list) print_obj(obj);

        };

   };

Functions calls are object instantiations, which create activities doing the described printing jobs.

In Transframe, the concept of function has been unified into the class concept. A traditional function is just a specific case of the Transframe's functional object instantiation. Transframe has a class named func to support the equivalent C++ function call protocol, which is an efficient and sequential procedure using the same stack frame technique. Transframe's “functions” are not limited to sequential functions, however. User can define other classes such as threads, processes and remote procedures to implement their own function call protocols. For example, the following declaration:

   thread print (object_list: ...)
   {
      foreach (obj in object_list) print_obj(obj);
   }

defines a class print whose superclass is a user defined class, thread. Calling print shall create a concurrent thread to do the print job.

Each class has a default class parameter: InputType, which is a tuple type that defines the type of the class’ object instantiation interface. The class functional has an additional class parameter, OutputType, which defines the type of the output of the function. The default output of non-functional is the object of the class itself (selfclass). The meta function create defines the way how the object is instantiated.

Let us consider a user-defined function class that can be called in multicast. In the subscriber-publisher pattern, the publisher may need a notify function being invoked for all its subscribers. We can define a multicast function as below:

   class multicast <F: type of func <OutputType=int>>: public functional <InputType=F.InputType; OutputType=int>

   {

      private:

         func_list: type of F [];

 

      meta public:

         func create (owner: any; entry: pointer; input: F.InputType): int

         {

            res :=0;

            foreach (f in func_list) res += f(input);

            return res;

         }

 

      public:

         func + (f: type of F)

         {

            func_list.append(f);

         }

   }

The class multicast is a subclass of functional whose OutputType is bound to int. Its InputType is bound to the input type of its own class parameter, F, which is a function that outputs an integer. The class multicast overrides the object instantiation (function call) by the meta function create. When an activity object of multicast is created, the activity calls registered functions in the func_list, and returns an integer to indicate the number of successful calls. The function + is used to register subscriber’s function.

Using this pattern, a publisher can simply define a multicast function as following:

   class Publisher

   {

      public:

         multicast publish <Subscriber::func(msg: Message):int> {};

 

         func do_something()

         {

            msg: Message;

              // prepare/format message

            publish (msg);

         }

   }

The multicast function publish bounds the class parameter F to a member function type of Subscriber that handles a particular message: Subscriber::func (msg:message):int. When a subscriber subscribes the publisher, it simply adds its own message handler function:

   class Subscriber

   {

      func messageHandler(msg: Message):int

      {

         // handles the message and returns 0 or 1 to indicate whether the message is handled

      }

      func subscribe(p: Publisher)

      {

         // function signature is statically checked here

         p.publish += messagehandler;

      }

   }

The interface of the message handler provided by the subscriber is checked to match the function interface specified in the publisher.

In contrast, C# introduced a new language concept, delegate, for multicast usage. However, the C# language itself cannot describe the interface of the Delegate Class. Instead, it relies on the system and the compiler to interpret the semantics and usage of delegate. Multicast is just one of many application specific features and should not be built in the language core. Because of the concept integration, Transframe provides a more powerful and flexible mechanism yet a much simpler notion without introducing any built-in feature.

Function Type and Anonymous Function

 In Transframe, you simply declare a type name that refers to a function (type). For example:

   compare: type of func <T:Comparable>(first: T; second: T): bool;

or,

   compare: #func <T:Comparable>(first: T; second: T): bool;

The name compare refers to a function (a subclass) that has the same object instantiation interface as specified in the declaration. The following function:

   func CompareEqual <T:Comparable>(first: T; second: T): bool { return first == second; };

has an interface that matches the one in the compare type declaration, you can assign this function to compare:

   compare = CompareEqual;

You can also assign an anonymous function that has a matched interface:

   compare = func <T:Comparable>(first: T; second: T): bool { return first == second; };

Because the interface of the function is already defined in the compare type, you can omit the interface specification in the anonymous function (if you agree to use the same parameter names in the interface):

   compare = { return first == second; };

If the function body contains a single return expression, you can simply write the only expression:

   compare = first == second;
   compare = first > second;
 

Interface with Multiple Sections

You often see the following C++ STL (C++ Standard Template Library) code:

   remove_if ( collection.begin(),collection.end(),

               compose_f_gx_hx ( logical_and<bool>(),

                                 bind2nd ( greater<int>(), 4),

                                 bind2nd ( less<int>(), 7)));

where collection is a container of integers and the function remove_if removes all integers between 4 and 7 from the container. The above code is very complicated and difficult to understand. In Transframe, you simply write:

   remove from collection.begin() to collection.end() if ele > 4 and ele < 7;

The expression is much more readable, and faithfully shapes the true meaning of our intention. How it is possible, you may wonder, that the above expression is written without any syntax sugar or built-in feature for this particular case?  Let’s examine the function interface defined in the remove-from-to-if function:

   func remove <T: Iterator >

        from (start: T)

        to (finish: T)

        if (condition: type of func (ele: T):bool)

   {

      while (start!=finish)

      {

         if (condition(start)) (start++).delete();

         else ++start;

      }

   }

The function remove-from-to-if has a multi-section name in four sections separated by input interface sections. When we call the function, sections of the function name will be interlaced in the same order with actual parameters for the input. Assuming that we have a string set:

   strings: set<string> = (“first”, “second”, “third”);

Then, we can call the function remove-from-to-if by the expression as below:

   remove from strings.begin() to strings.end() if ele.startWith(‘f’) or ele.endWith(‘d’);

The condition function supplied in this call is a simplified anonymous function as described previously.

In the simplified form of anonymous function passed as an actual parameter to a function call, object scopes of the parameter are automatically open. In the example above, the parameter in the function interface is ele, which is the type of set<string>. When the object scope of ele is open, you can access the member of ele directly in expression:

   remove from strings.begin() to strings.end() if startWith(‘f’) or endWith(‘d’);

However, if there is any ambiguity caused by more than one opened object scopes, you will need to use the explicit parameter name for member name resolution.

C# claimed taking a general approach to integrate database query facilities into the language but still, special syntax sugar has to be used. For instance, the following query expression:

   from student in students

   where student.age<14 && student.grade>9

   select new { student.name, student.age, student.grade };

is actually wrapped in a syntax sugar and will be translated into not-so-friendly C# expressions:

   students

   .Where (student => student.age<14 && student.grade>9)

   .Select (student => new { student.name, student.age, student.grade } );

Transframe does not add any syntax sugar for specific application domain. Language features are introduced only when they have a global benefit to all application domains. Without using any specific syntax sugar for database query, you can write the database query in a Transframe expression in the following function call:

   students

   where age<14 && grade>9

   select (name, age, grade);

where-select is just a regular user-defined member function in the Container class:

   class Container <ElementType : any>

   {

     

      func where <SelectType: any> (filter: type of func (ele: ElementType): bool)

           select (selector: type of func (ele: ElementType): SelectType)

           : Container<SelectType>;

   };

Assuming that students are a table of tuple type:

   students: table of (name:string; age: int; grade: int; school: string; …);

where table is a subclass of Container. We can call its where-select function in the following format:

   result:= students

            where ele.age<14 && ele.grade>9

            select (ele.name, ele.age, ele.grade);

The result will be the type of table of (string,int,int), which is determined by the output type of the anonymous function supplied after select.

Further, when the scope of the parameter of ele in the type of (name:string; age: int; grade: int; school: string; …) is open, you can access the member of ele directly in expression:

   result:= students

            where age<14 && grade>9

            select (name, age, grade);

 

Class Type

The concept of function type is only the special case of a class type because function is a class. Features described in the above can be equally applied to a non-function type, i.e. a class type. Consider the following example:

   class co_routing

   {

      private:

         thread_conext: ThreadContext;

 

      meta public:

         func implicit create (owner: any; self: pointer<thisclass>; entry: address; input: InputType)

                     : pointer <thisclass>

         {

            self.thread_context = system.creatThreadStack(owner,entry,input);

            system.startThread(self.thread_context);

            return self;

         }

   }

The class co_routing is not a function. It overrides the object instantiation method create by creating a concurrent thread to execute its instantiation process. We can use the class type in a function that performs computation on a list of sub-ranges in parallel:

   func parallel on ( r: Range ) do ( ro: type of co_routing ( r: range ) )

   {

      range_list:= r.split();

      context_list: ThreadContext [];

      foreach sub_range in range_list do context_list += ro(sub_range).getContext();

      system.threadJoin(context_list);

   }

When we call the function, we simply provide an anonymous co_routing class:

   parallel on Range (0..n, 0..m) do M1 * M2 | r;

The anonymous class is equivalent to:

   co_routing anonymous (r: range) { M1 * M2 | r;  }

and the expression “M1 * M2 | r is a call to a Matrix’s member function with multi-section name “* |  that multiplies itself with another matrix on the given sub-range r.

Multiple Outputs

When more than one object references must be returned from a routine, there is no direct way for a Java method to provide such interface. Let's consider an example: given a URL resource address, we need a method to decompose the resource address into three strings: the path, the resource file name, and the resource type name. That is, for an address:

        "http://www.foo.com/images/blue_sky.jpg"

we would like to use this method to break the address into the following three strings:

        "http://www.foo.com/images/"
        "blue_sky"
        "jpg"

In C++, we can write a function with the following interface: 

1              void decompose ( const char *url_address,
2                              char *&url_path,
3                              char *&resource_name,
4                              char *&resource_type
5                      );

The C++ interface declares the three outputs in terms of reference of pointers. It is explicit but difficult to understand. What does "*&" mean? Should I write "*&" or "&*"? Java does not have a much simpler way to describe this interface, either array or extra class should be introduced as the following example:

1              void decompose ( String url_address,
2                               String[2] decompose_result
3                              );

The Java method interface does not have any improvement in readability. People have to guess the meaning of expressions like decomposite_result[1]. The worse, if elements of the array must be in different types, the type of each elements must also be guessed. Since Transframe support tuple type constructor, a Transframe decompose function can be written as the following:

1              function decompose                    
2                      (url_address: String):
3                              (       url_path: String;
4                                     resource_name:  String;
5                                     resource_type: String
6                              );
7                                             

The output type of the decompose function is a tuple that contains three strings, which is more readable than C++ and Java's interface. The function can be called in the following way:

1       path, rname, rtype: String;
2       (path, rname, rtype) := decompose(url_path);

Scope and the Beyond

Scope

A class declaration specifies the scope of the object. The object scope is a geometric framework used to hold a set of component objects. This geometric framework is described by a number of local name declarations within the body of the object’s class. Objects (and classes as well) referred by these local names are member objects (and member classes).  For instance,

   class Geometry

   {

      public:

         center: (float, float, float);

         scale: (float, float, float);

         pivot: (float,float,float);

         axis: (float,float,float);

         angle: float;

   }

The class Geometry declares members of center, scale, pivot, axis, and angle. After an object of Geometry is created by calling the class’ constructor:

   g := Geometry();

A member object can be visited by giving an expression of its enclosing object as well as its associated local name:

   g.center = (0,0,0);­

 

A Transframe has two default scopes: a meta scope for members of class itself and a default object scope for members of the object instantiated by the class.

User-Defined Object Scope

An object can have multiple object scopes. Object created in different object scope has a separate collection of name declarations and a separate object code. For instance, we can define a remote scope in the class Server:

   class Server
   {
      scope remote;
   }

The scope remote is user-defined object scope. An object created by the class Server can be different depending which object scope is selected – it can be a server with default local object scope or a server with remote scope. Each object scope has its own constructor and set of data and class/function members. The choice of scope to be used is explicitly declared when the class is used in name declaration. For instance:

   class Warehouse: public Server { … };
   warehouse: Warehouse;
   warehouse_remote: Warehouse in remote;

The name warehouse  refers to an warehouse object with the default object scope that implements a set of server functions, while the name warehouse_remote refers to an warehouse object in remote scope that acts as a server stub for the client side and provides client functions that internally transfers the client calls to its server side for execution. Names defined in different object scopes cannot be cross-used. For instance, a function defined in remote scope cannot be called directly from the default object scope. However, the automatic stub generation mechanism in Transframe’s access control declaration will provide a transparent bridge for cross usage among different object scopes.

Access Control

Like C++, Transframe provides access controls such as public, protected, and private, which can be combined with a scope name to define detailed access control protocols, for instance remote public, meta private, local protected.

A user defined access control can be introduced by access control declaration. For example:

   class Server
   {
      scope remote;
      access services = { protected };
      access callbacks = { remote, protected };
      access service_stubs = { remote, public };
      access callback_stubs = { protected };
   }

Automatic Stubs to Across Different Object Scopes

You can also use the access control to specify the rule in which how the contents are automatically generated.

   class Server
   {
      scope remote;
      access services = { protected };
      access callbacks = { remote, protected };
      access service_stubs = { remote, public, auto service_stub from service in services };
      access callback_stubs = { protected, auto callback_stub from callback in callbacks };
 
      services:
   
         class service { … }
 
      callbacks:
 
         class callback { … }
 
      service_stubs:
 
         class service_stub { … }
 
      callback_stubs:
 
         class callback_stub { … }
   }

The class Server specifies four member classes: service, callback, service_stub, and callback_stub. They are used to define concrete member classes such as services and callbacks in the derived classes. Service stubs and callback stubs will be automatically generated from services and callbacks in derived classes. Consider an example:

   class Warehouse: public Service
   {
      services:
 
         service request (request_id: RID; item: ItemID; quantity: int);
         service cancel (request_id: RID);
         service modify (request_id: RID; quantity:int);
 
      callbacks:
 
         callback grant (request_id: RID; item: ItemID; quantity: int);
         callback reject (request_id: RID; item: ItemID; quantity: int);
   }

The class Warehouse has two object scopes: one is the default object scope in which all services are defined, and another is the remote object scope in which all callbacks are defined. In the remote scope, service stubs for request, cancel, and modify are automatically generated and can be called from an object at client side (with remote scope.)  On the other way around, callback stubs for grant and reject are generated automatically so that they can be called from the object at server side (with local scope).  Consider,

   class Warehouse: public Service
   {
      services:
 
         service request (request_id: RID; item: ItemID; quantity: int)
         {
            available := check_inventory (item, quantity);
            if (available > 0)
               grant (request_id, item, available);
            if (available < quantity)
               reject (request_id, item, quantity-available);
         }
         
   }

The function grant and reject are called directly from the server side as if they were already defined in the default object scope. However, the actual function called is the respective callback stub automatically generated for the callback in the remote object scope.

For a warehouse in remote:

   warehouse: Warehouse in remote;

We can simply call its request service by:

   warehouse.request(id,item);

As if the request service were already defined in the object’s remote scope. Internally, the actual function called is the respective service stub automatically generated for the service in the default object scope.