Back Contents Next

11. Object Forms

This section leaves the area of fundamental language features and describes non-fundamental features. It focuses on how traditional object oriented structures like modules, properties, methods/functions, classes and interfaces find expression. These constructs may be expressed by the pure fundamental features and that will be presented. However, many have non-fundamental support features which will be covered as well.

11.1 Modules

Modules which are sometimes called packages or name spaces in other languages are the basic means of grouping other entities on the large scale. Often programmers new to the concept ignore them, this is a grave mistake as they are very useful. Modules are expressed as named objects of an anonymous type that don't declare any super or interface types. The distinction of what is a module is made only for the scope access modifier (see 7.2.2 Scope). Modules by there object nature may be nested, may have access modifiers and follow all the rules therein.

public outside
{
    // Outer module contents
    public inside
    {
        // Inner module contents
    }
    // More outer module contents
}

A program is usually organized by modules which can contain many other modules and objects. If all modules had to be nested like this then the whole program would have to be written in a single file. To allow for multiple files to contain different parts of a module, the outer most module of a file is open and each file can provide a view onto some part of such a module. When a program is compiled it as if all the files are joined together to create a single module declaration in a large file. As an example consider:

// Code in file A
public mainModule
{
    public dog
    {
    }
}
// Code in file B
public mainModule
{
    public cat
    {
    }
}

This example declares a module mainModule which contains two modules dog and cat. This example brings up the question of how nesting of modules is handled across files. As in the example above modules may be nested by placing them in the block of another module. Could the module dog have something more added to it by declaring another file with the module dog nested in mainModule? The answer is no. This is because modules written totally enclosed in another like this, as most entities, are considered closed and can not be added to outside of their original declarations. So how are deeply nested modules expressed? Modules may be nested but left open by using the member selection operator between the names of modules that are nested. An example may be helpful here:

// Code in file A
public root.horse
{
}
// Code in file B
root.horse
{
}
// Code in file C
public root.horse.leg
{
}

These three files create a module root with the module horse as a member. The horse module then has the leg module as a member. Since the 'root.horse' module is left open in file A, file B is free to add to that module. File C simultaneously provides the contents of the 'root.horse.leg' module and declares that the leg module is a member of the 'root.horse' module. Note that if we had tried to declare the leg module in one of the first two files then it would have been closed and file C would have been unable to add to it. This is valid only on the most outer module declaration because it is this declaration that provides the files position in the larger module hierarchy.

Modules must be named objects which do not declare a super type or any interface types. If the first entity encountered in a file does not meet this criteria then it is taken as an entity that is not enclosed in a module (i.e.. at the top level). If the module is not used as an object, i.e.. references to it not stored or passed then no object overhead will be generated. Warnings will be generated for modules which are used in these ways.

Modules and Creation/Destruction

In addition to the concept of open objects, modules allow multiple destruction blocks. Each file may contain one destruction block, all the destruction blocks are combined into a single block in unspecified order when the program is compiled. Creation code from each file is also executed at creation time but in an unspecified order relative to creation code in another file of that module.

Modules and Access Modifiers

When using open modules it is necessary to define the access to be given to each module involved. If an access modifier is given as in the example above in file C then it refers to the most inner module (in that case leg). If a modifier is omitted then instead of defaulting to personal it is assumed that the access is specified in another file. This is why file B in the above example doesn't have a modifier, it doesn't need one because access has already been specified in file A. If no access modifier is specified in any file containing a module then it is an error, for this reason one may want to create files simply to specify modules.

11.1.1 Import Statements

Modules provide a hierarchal organization for programs. Using the member selection operator it is possible to fully qualify access to any entity in a module or indeed in any object. But this can get tedious and cause problems when module names change or the module being used for a particular purpose is switched. For that reason import statements can be used to bring names from another module and indeed from any named object into scope so that they can be used without fully qualifying them. Import statements can be used to bring all or only a few names in from an object. They bring in the names of all named entities in some scope, they do not bring names in from inside patterns. There are essentially four forms of import statements:

import pet.dog.*;
import pet.fish.*.*;
import pet.cat.Persian;
import pet.turtle.**;

The first form makes all names declared in the 'pet.dog' module available. The '*' can be thought of as the wild card character which matches anything. The second form makes all names in the 'pet.fish' module available but also makes all names in all sub-modules of 'pet.fish' available. This can be extended to any depth by adding more periods and stars. The first two forms bring in many names at once. The third form can be used for more selective importing. This imports only the entity named Persian from the 'pet.cat' module. That is only the name Persian is imported. The fourth form allows all names in a module and all sub-modules to any depth to be imported in one statement. This can save typing many dots and asterisks but can also be useful when you don't know how deep a module nesting may go. Import statements treat all named objects the same.

Import statements may be placed anywhere in a file and bring the names into scope beginning at the import statement until the closing curly brace of the block the import statement is in. The name is available to sub-modules and objects in that region. It is important to note that for lexically contained entities to have access to the names they must be declared after the import statements whether or not the whole scope would normally be searched. Names inside a scope declared after the import statement hide imported names. Imported names cause a name collision with names already declared in the scope. Names imported in a deeper scope hide names imported in a more shallow scope because of the scoping rules..

Name Collisions

It may happen that two modules contain the same name and both are imported. Or, the name has already been declared in the scope. If this occurs at different depths it is not a problem (see above). However if the import statements occur at the same depth then it is an error to attempt to use the names that have collided without qualifying them. Note that it is not an error for names to collide only to use names that have been involved in a collision without fully qualifying them.

11.2 Properties

A property is a named entity with a named type. A property may be assigned a value that is of a compatible type and yields a value of the type specified by the property.

x :char;
y :byte = 42;
z :float = 3.14;
f :()-> int = random;

Properties are references to or aliases for entities.

11.3 Methods

A method is the form of object used to perform most actions and to communicate with objects. Methods are named patterns of an anonymous type which don't return self. They cannot be assigned to. As an example:

public add(a :int, b :int)-> int
{
    return a + b;
}

This declares a method add which takes two integers and returns their sum. When a method is invoked it creates an object from the pattern body and runs the initialization code. That object is destroyed as soon as execution leaves the initialization code. The exception is if the method stores a reference to self or returns it. If the method is not used as an object (i.e.. self is not returned or stored) then no object overhead will be generated and it will be equivalent to methods in other languages. Warnings will be generated when a method is used in these ways. It is fine to assign a method to a pattern property, this is equivalent to method pointers.

11.3.1 Functions

When a method appears in a module it acts like a function in other languages, and has access to its surrounding module. No method overhead is generated for this, even if it is assigned to a pattern property. This is equivalent to a function pointer.

11.3.2 Default Parameters

When declaring a method it is possible to specify default values for one or more parameters. This can be any expression yielding an appropriately typed value. This is done just as one would assign a value to a property:

public test(amount :int = 10)-> void
{
    //Method body
}

Default parameters are short cuts for method overloading. So the above example is equivalent to:

public test(amount :int)-> void
{
    //Method Body
}

public test()-> void
{
    return test(10);
}

If a default parameter is provided then it must be provided either for all parameters after or all parameters before that one. That is default values cannot be provided for a parameters in the middle of the parameter list. When default parameters are called they operate as in the above example. Each form with fewer parameters calls the one with more in the order necessary (left to right if either order works). This means that default parameters can contain complex expressions whose value will change and they will be evaluated each time the method is called.

public demo(a :int = 1, b :int = 2, c :int)-> int
{
    //Method Body
}

is equivalent to:

public demo(a :int, b :int, c :int)-> int
{
    //Method Body
}

public demo(b :int, c :int)-> int
{
    return demo(1, b, c);
}

public demo(c :int) -> int
{
    return demo(2, c);
}

Default parameters cause the same ambiguities as overloaded methods. For resolving ambiguities, default parameters are considered the same as overloaded methods so they do not provide help in disambiguating invocations. Because of the form of overloading one can't provide values for some defaulted parameters while not providing values for defaulted parameters in the middle.

11.4 Classes

A class is a named pattern with an anonymous type that returns self. Classes are the means through which types are defined. If the class is not used in function properties then no overhead will be created for this. Warnings will be generated when classes are used in this way.

11.4.1 Class Keyword

Many programmers coming from backgrounds in C++ and Java will find the syntax for declaring types to be unfamiliar and easily confused with that of declaring methods. For those reasons Opal provides the 'class' keyword. The class keyword can be used to declare class patterns and make it more apparent that a type is being named. To do so the class keyword is written followed by the type name (and any super/interface types see Extending types). Then the pattern body. The pattern body is written without a parameter or return type declaration. Instead the parameters are written immediately after the class name. The return type is assumed to be self. This is exactly equivalent to the normal pattern declaration technique. As an example:

class MyClass(a :int) :Super
{
    // pattern body
}

is essentially equivalent to:

MyClass :Super(a :int) -> self
{
    // pattern body
}

11.4.2 Create Functions/Methods

In languages which provide poor support for object initialization it is often standard practice to require an initialization method to be called on every object after it is created. This is too easily forgotten and leads to many errors. In addition the language can not take advantage of the knowledge that such an initialization method must be called. For these reasons Opal has create functions. By default a pattern which names a type may be invoked to create instances of that type. It is also given an implicit create function which takes the same parameters and invokes the object with those parameters. The implict create function is located inside a module with the same name as the pattern and in the same scope as the pattern.

Once a create function is explicitly added to a type object it is illegal to invoke the object to create instances, instead the create functions must be used. This insures that a create function used to instaniate every object which needs it. Create functions are generally declared in a module with the same name as the pattern and in the same scop as the pattern. They are just normal functions except that the function name is preceded by the keyword 'create' and the function name may be absent. Creation functions may be overloaded and a function/method may be overloaded based on whether it is a create function or not.

Create function may leave off the return type declaration but not the '->' operator. In this case it is assumed that the return type is that of the type sharing the name and scope of the object it is declared in. Such create funtions may be invoked like so 'create Class.functionNamee(...);'. If no name is given to the create method then it may be invoked as 'create Class(...);'. Notice that this is consistent with the fact that they are functions in a module with the same name as the pattern. Inside the create method the pattern may be invoked to create instances (as 'this(...)'). This is the case when a create method could be declared with an implict return type. Note that the create method as private access to the object it is creating becuase an object and pattern with the same name in the same scope are considered to be nearly one scope (see 7. Scoping).

Create functions are not required to create a new instance, they may return a preexisting instance. They are essentially factory functions which have been built into the language. After creating an instance they may be used to further initialize the object. Opal is aware of this. It allows properties to be left without a value after the pattern invocation if all relevant creation methods assign an appropriate value to it.

While the main use of create is, as above, to function like constructors in Java and C++ it may also be used for factory methods. If a creation method is written in an object it may still use the implicit return type, alternatively it may use a type with no relation to the enclosing object. Such create methods are invoked as 'create object.methodName(...)'. It is recommended that create methods of this form always be given a name though it is not required. An example of this use is in the create clone() method of Object.

11.5 Interfaces

The class keyword provides a simple way to declare classes. However, sometimes one writes a pattern that is meant only for implementing the interface of. This can be expressed by the pattern keyword which has the same syntax as the class keyword without the parameters. The pattern is abstract and all the patterns contained in it are abstract without bodies. Neither should be declared abstract because they are already abstract by being in the interface. In addition no pattern bodies should be declared in an interface. No objects should be declared in interfaces either. No members of an interface may be private or personal. In addition interface members may not be final as this would prevent overriding. As an example:

interface MyInterface ::Interface
{
    // interface body
}

is essentially equivalent to:

MyInterface ::Interface()-> self
{
    // interface body
}

Interfaces must be implemented and not extended. Also an interface may only implement the interface of other types (not extend).



Back Contents Next

jwalker@cs.oberlin.edu