Jura Home | John Winn, December 2003 |
The ability to re-use code in a number of different contexts is a very desirable property for a language. It significantly reduces development time and makes programs higher quality, because the code is effectively retested each time it is reused . It is therefore important to be able to make code as re-usable as possible.
If we wish to write a generic algorithm for e.g. sorting, then we want
to be able to any objects which can be compared to each other, and to any data
structure which maintains an ordering on some objects. Java goes some way
to allowing this by providing the Comparable
interface which allows
to objects to be compared and the List
interface which maintains an
ordered set of objects. The Comparable
interface is a
very small interface with just one method compareTo(Object o)
which
is very precisely defined in the documentation. I call such a small
interface an ability because it represents the ability to do one
particular thing (in this case, the ability to compare the object with another)
and that thing is defined in detail. It follows that abilities are very
small interfaces which represent a single facet of functionality - they
typically have names ending in-able
, but this is not
required.
The Java List
interface is not an ability but a rather
bloated interface with lots of features like sub-lists, iterators, array
conversion, index-of operators etc. Implementing List
fully
takes a great deal of effort - usually methods which are rarely used are either
left unimplemented or are implemented poorly and left untested. It makes
sense to break List
down into a set of abilities, each of which
represents some independent functionality of the list. These could include
CanAdd
(the ability to add elements), Iterable
(the
ability to provide an iterator for iterating over contained elements), Container
(the ability to check if an element is contained in this object). The
interfaces List
and ModifiableList
can then be used to
represent sets of abilities. This approach is used in Jura to allow
maximum re-use of code.
Note: Some languages use dynamic typing to achieve code re-use. This is dangerous as, just because a class has a method of a particular signature, doesn't mean that the method does what is expected. By implementing an ability interface, the class is stating that it not only has a method of the right signature but also that the method fulfills the terms of the contract of that ability.
They key to effective use of abilities is that a number of generic abilities should be defined in the core classes of a language. Third party classes should then implement these abilities as appropriate and define new domain-specific abilities. Ideally, almost no functionality should be defined unless it fulfils an ability.
Jura provides a number of core abilities in the jura.lang.ability
package, which include:
Ability name | Method | Description |
---|---|---|
Initialisable |
void initialise() |
After an instance of a class has been created, initialise()
will be called automatically if it implements this ability. |
Validatable |
void validate() throws Exception |
An class whose state can be validated should implement this ability. It will be called automatically (following any creation operations, like setting of properties) during creation of an instance of the class and should throw an exception if the instance has invalid state. It can also be called at other times when the state of the object need be validated. |
Invokable |
Object invoke(Context c, List arguments) |
Ability of an object which can be invoked with some fixed number of arguments. |
HasName |
String getName() |
Ability of an object which has a name, which typically is unique to this object in a particular context. |
EntryPoint |
void main() |
Ability of an object to act as an entry point for an application. |
Jura defines collection classes in the jura.util
package.
Corresponding abilities are in the jura.util.ability
package.
Almost all collection abilities use generics to indicate the element type of the
collection. The most basic collection abilities are listed below, property
names are listed in the braces.
Ability name | Method | Description |
---|---|---|
Iterable{elementType} |
Iterator{elementType} iterator() |
The ability of a collection to allow iteration over that collection
using an Iterator object. |
Clearable |
void clear() |
The ability of a collection to allow all elements to be removed. |
CanAdd{elementType} |
boolean add(elementType obj) |
The ability to add an object of type Type to the collection. |
CanInsert{elementType} |
void add(Integer index,elementType obj) |
The ability to insert an object into an ordered collection. |
CanRemove{elementType} |
boolean remove(elementType obj) |
The ability to remove an object of type Type from the collection. |
CanGet{keyType,valueType} |
valueType get(keyType key) |
The ability to get an object of type Type2 from the collection based on a key of type Type1. |
CanPut{keyType,valueType} |
void put(keyType key,valueType value) |
The ability to put an object of type Type2 into a collection based on a key of type Type1. |
Container{elementType} |
boolean contains(elementType obj) |
Allows testing to see if the object contains a particular element. |
HasSize |
int getSize() |
Allows determining the size of an object. |
HasElementType |
Type getElementType() |
Ability of a generic collection template for collections which have typed elements. |
Collection interfaces then implement sets of abilities.
Collection Interface | Extends | Description |
---|---|---|
Collection{elementType} |
HasSize, Container{elementType}, Iterable{elementType} |
A collection of a known number of elements. |
Map{keyType,valueType} |
Collection{Map.Entry{keyType,valueType}}, CanGet{keyType,valueType} |
A map with a known number of map entries. |
List{elementType} |
Collection{elementType}, CanGet{Integer,elementType} |
A collection with a known ordering. |
ModifiableCollection{elementType} |
Collection{elementType}, CanAdd{elementType},
CanRemove{elementType}, Clearable |
A collection which can be modified. |
ModifiableMap{keyType,valueType} |
ModifiableCollection{Map.Entry{keyType,valueType}}, |
A map which can be modified. |
ModifiableList{elementType} |
ModifiableCollection{elementType},List{elementType},
CanPut{Integer,elementType} |
A list which can be modified. |
Note that the Map
interface is defined to be a collection of map
entries (for consistency with the Java Map
interface). However, the CanGet
ability is more flexible and can be implemented
by collection of other objects, such as List
which uses CanGet
to provide an ordering based on mapping from integers to objects, but does not
itself implement the Map
interface.
Note: The prototype abilities and interfaces may not be exactly as stated here.
When writing a general purpose algorithm, it is best to test for abilities using IfCast
(or similar)
rather than full-size interfaces unless all the abilities on that interface are
necessary for the algorithm.
As a simple example, this algorithm will add the supplied string to a container if it is not already in the container.
Method:addNoDuplicates{ returns=Boolean Par:c{type=Container{elementType=String}} Par:s{type=String} // If{test=c.contains(s) // Return{false}} IfCast{var=c type=CanAdd{elementType=String} // {* c is now cast to CanAdd *} c.add(s) Else {* c does not implement CanAdd{String}, so throw an exception. *} Throw{new UnsupportedOperationException{msg="Cannot add strings to "+c}} } Return{true} }