Back Contents Next

4. Patterns

It might seem that objects would be the only type of entity needed. However, patterns are invaluable. A pattern describes an object and can be used to instantiate (that is make) many objects like that. These objects are the pattern's instances. Patterns are to objects as cookie cutters are to cookies.

As entities and object types may be named or anonymous, so patterns may be also. That is, a pattern, as any entity, may be named or anonymous. In addition, a pattern's type may be named or anonymous. The situation is more complex with patterns because they are used to name new types.

Just as an object name may be assigned to different objects, so a pattern name may be assigned to different patterns, within the limits of types.

A pattern is distinguished from an object by the fact that while an object holds other entities a pattern may be invoked. Pattern declarations can be recognized by their parameter list and return type which describes how they may be invoked and what they result in when invoked. These have the form:

(access-modifier param-name :param-type, ...)-> return-type

A pattern body follows this. A pattern body is an object body except that the object described is not existent till the pattern is invoked. Whenever a pattern is invoked all the entities described in the pattern body are created inside a new object of the form described by the pattern.

4.1 Pattern Parameters

Pattern parameters are entities. They are treated as if they were listed before anything in the pattern body. So that all objects instantiated from the pattern contain them as members. Parameters are named entities of a named type without a value. They receive their values when the pattern is invoked.

The access modifier may be omitted before a pattern parameter in which case the default access (personal) will be used.

Sometimes pattern parameters are not used inside a pattern body, though this is generally not a good idea. In the case that a parameter is unused its name may be omitted to indicate this. This also prevents cluttering the name space with meaningless names. This turns the parameter into an anonymous object of a named type and so it is no longer a member but simply a conversion expression which is executed to no effect (this does not require any more overhead then the normal parameter passing and will save space in the instantiated objects).

4.2 Named Patterns

A named pattern is a pattern with a name. Named patterns just as other named entities may appear in an object body.

4.2.1 Anonymous  Types

The most common form of pattern is named with an anonymous type. The syntax for this is:

access-modifier name :super-type :interface-1, interface-2 ... (access-modifier param-name :param-type, ...)-> return-type
{
    pattern-body
}

When such a pattern is invoked the parameters are assigned the values provided by the caller and the object described by the pattern body is created. Note that the parameters are part of this object, it is as if they were declared before any of the content of the object body. The newly created object would be an anonymous sub-type of the super type and implement the interface types. However, because the pattern describes objects of that form it names the type. All patterns of this form name the type or class of the objects they instantiate. Note that the pattern's type combines not just the super type and interface types but also the number and type of parameters and the return type.

Since the pattern type is anonymous, patterns of this form can't be assigned to.

4.2.2 Returning Self

Though any pattern creates an object when it is invoked, some objects return themselves. In this way they become factories for that type of object. It turns out to be important to have a way to make this fact explicit. This can be done by declaring the return type of a pattern to be the special keyword 'self' (see 7.4.1 Self Keyword). The syntax is then:

access-modifier name :super-type :interface-1, interface-2 ... (access-modifier param-name :param-type, ...)-> self
{
    pattern-body
}

Behavior of patterns of this form is no different than other patterns. An exception is with return statements (see 6.2.1 Return Statement).

4.2.3 Pattern Types

Pattern types as alluded to by the last section depend mostly on the number and type of parameters to the pattern as well as the return type. Patterns depend on the super and interface types only in the case that a new pattern is being described. Thus generalized pattern types depend only on the parameter and return types. A pattern type may be expressed as:

(param-type, ...)-> return-type

Notice that pattern types are really just composite types.

4.2.4 Named Types

Now that we know what a pattern type looks like, named patterns with named types can be described. These, just as named objects with named type, have only a single type. They may be assigned to different values during the execution of the program. They may be left unassigned so long as they are assigned before being used. The syntax for them is:

access-modifier name :(param-type, ...)-> return-type = expression;

Where the expression must yield a pattern of that type. The line is terminated by a semicolon because it is an assignment statement as well as a pattern declaration. The pattern type may not be omitted because it would then be ambiguous.

4.2.5 Pattern Overloading

Patterns may be overloaded. Since a pattern may be invoked, two patterns with the same name may be distinguished by their invocations if they take a different type or number of parameters. This is done by declaring two patterns with the same name but different parameter types, as:

public method() -> void
{
}

public method(a :int) -> int
{
}

There need be no particular correlation between overloaded patterns' return types. Patterns are first distinguished by the number of parameters they take. If as in this case each takes a different number of parameters then there can be no ambiguity over which pattern is meant when invoking.

If two overloaded methods take the same number of arguments then when invoked it must be unambiguous which one is meant. To determine which pattern is being invoked when they have the same number of arguments the parameters are checked for compatible type. If a pattern requires an incompatible type for one of its parameters then it is eliminated. Among remaining patterns ones which do not require a conversion are preferred to those which do.

It is almost always possible to make an ambiguous call to an overloaded pattern because it would be possible to define an object which implemented the interface of both parameter types to an overloaded pattern. It is an error to make an ambiguous call to an overloaded pattern. It is recommended that overloading be used in a way that minimizes ambiguity. Don't overload on two types in the same hierarchy. It is best to overload on number of arguments. If this can't be done all but one should take types which are final (see 8.1 Final). Note there is no limit to how many times a method may be overloaded.

4.3 Anonymous Patterns

Anonymous Patterns are patterns without a name. They form expressions dealing with patterns. Remember that the colon introducing the super type is always required with anonymous patterns.

4.3.1 Pattern Literals

Anonymous patterns with anonymous types form pattern literals. A pattern literal is a pattern value which is directly expressed, as:

:super-type :interface-1, interface-2 ... (access-modifier param-name :param-type, ...)-> return-type
{
    pattern-body
}

4.3.3 Pattern Conversion Expressions

Just as anonymous objects of named types form conversion expressions, so anonymous patterns of named types form pattern conversion expressions. A pattern conversion expression can be used to change the pattern type of an expression to a type which can be determined compatible at compile time. This is because it follows the assignment semantics. A pattern conversion expression has the syntax:

:(param-type, ...)-> return-type = expression

Extra parentheses will often be required because of the low precedence of assignment. As a more concrete example consider the statement:

obj = (:()-> Object = someFunc)();

In this example the function is converted to be a function returning object then is invoked. Pattern conversion expressions are useful since they can be used to select one form of an overloaded method because they follow the assignment semantics.



Back Contents Next

jwalker@cs.oberlin.edu