Jura Home | John Winn, December 2003 |
Generics are classes or interfaces which take one or more type parameters
e.g. List
{elementType=String
} is a generic List class which takes
a single type parameter (in this case String
) indicating the type
of the elements contained in the list. In general, these
parameters are then used instead of type names throughout the definition of the
class or interface.
In Jura, a generic class or interface is an extension of Class
or Interface
respectively which has additional properties
corresponding to the generic parameters. Jura does not restrict these
properties to be types - they can be of any Jura type. When the generic class is
constructed, it uses these properties to fill in the parameters of a template in a
similar fashion to the way Programmatic
Design Patterns are created.
To understand how generic types can be represented in Jura, remember that an instance of a type is represented as:
Type:name{operations on type instance}
For example, a Point
instance called p
is
represented as:
Point:p{x=0 y=0}
The part in bold is the type of the instance, the remainder sets the name and
properties (x and y) of that instance. Suppose the class Point
were
generic with a single type property coordType
for the type of the
co-ordinates (e.g. Integer, Float or Double). A Point
instance with Double type coordinates would then be written like this:
Point{coordType=Double}:p{x=0 y=0}
Now, both the type and the instance have properties. The part in bold
still defines the type and the remainder sets properties of that type.
Jura can tell the difference because any expression that appears directly before
an opening brace '{' or named brace ':name{' will be interpreted as a type. In
this example, both Point
and Point{coordType=Double}
are types. To clarify, the instance being defined has class Point{coordType=Double}
which in turn has class Point
. For comparison with Java, the
instance "Hello" has class String
which in turn a class Class
.
The difference in Jura is that this hierarchy can be extended with new generic
classes as needed. To see how this can be done, here is an example of
implementing a generic iterator interface.
Iterator
interfaceAn iterator object allows the elements in a collection to be iterated over,
e.g. to perform some operation on each. If the elements in the collection
are all of type T
, then the corresponding iterator should return
elements of type T
, rather than of type Object
(as
iterators do in Java). In other words, the iterator should use generics.
We can implement a generic iterator in Jura by extending the Interface
class to have an elementType
property and then, following
successful validation, generating methods on the interface using this property:
Class:Iterator{extends=Interface doc={"Allows iteration over a collection of objects."} Property:elementType{type=Type doc={"The type of elements that are iterated over."}} Method:validate{ doc={"Validates the iterator and constructs methods."} // super.validate() this={ Method:hasNext{returns=Boolean doc={"Returns true if there are additional elements to iterate over."}} Method:next{returns=$elementType doc={"Returns the next element in the collection."}} } } }
An instance of this generic type for iterating over String objects would then be represented as:
Iterator{elementType=String}
which is equivalent to the following interface
Interface:Iterator_String{ Method:hasNext{returns=Boolean doc={"Returns true if there are additional elements to iterate over."}} Method:next{returns=String doc={"Returns the next element in the collection."}} }
Generic classes can themselves implement interfaces (and so can have abilities). For example, in Jura, the
HasElementType
ability is implemented by many generic collection
classes.
A pattern, such as Template
, can be used to make the above
definition more readable:
Class:Iterator{extends=Interface doc={"Allows iteration over a collection of objects."} Property:elementType{type=Type doc={"The type of elements that are iterated over."}} Template{ Method:hasNext{returns=Boolean doc={"Returns true if there are additional elements to iterate over."}} Method:next{returns=$elementType doc={"Returns the next element in the collection."}} } }
This pattern transforms into exactly the same code as above. It also
allows for a separate validate()
method to be defined.
In fact, the above definition of Iterator is not quite complete as it does
not override the isAssignableFrom()
and isCastableFrom()
methods. If these methods are not overridden, then the follow code will
not compile:
Local:it1{type=Iterator{elementType=Object}} Local:it2{type=Iterator{elementType=String}} it1=it2 {* Will fail at compile time if isAssignableFrom() is not overridden *}
In Jura, many generic collection classes delegate to a class which implements
HasElementType
and provides correct implementations of isAssignableFrom()
and isCastableFrom()
.
For a collection class, isAssignableFrom()
should return true if
the supplied type is assignable to the generic type and its elementType
property is assignable from the elementType
of the supplied type.
In the prototype implementation, each generic instance is compiled to a separate Java class whose name is derived from the name and properties of that instance.