The JADE 2022 release meets tomorrow’s business demands.
The feature provides an extension to the existing interface support in the JADE language in the form of Generic interfaces. This new interface type will allow users to define interfaces with a list of generic type parameters, which are then resolved to concrete types when the interface is implemented. Generic interfaces are useful when dealing with collection classes by providing compile-time type safety and helping to maximise code reuse. Included in this feature will be a number of RootSchema generic interfaces, which will make it easier for developers to deal with collections.
The proposed feature set will include:
Support for Generic Interfaces
New RootSchema Generic Interfaces
Extension to foreach statements, to support custom collections
When defining a new JadeInterface an option will be available to mark the interface as generic. If marked as generic, one or more GenericTypes must be selected for the interface. GenericType is a new subclass of Type which will be used as a type placeholder until the generic interface is implemented and resolved with concrete Types. Each GenericType selected on a generic interface must be unique and must not clash with any other Class or JadeInterface names. Once the interface is created, it will be displayed in the Interface Browser in the following format:
GenericInterface<T1, T2, ..., Tn>
This format consists of interface name, followed by the type parameter section, delimited by the angle brackets (<>). It specifies the GenericType parameters: T1, T2, through to Tn.
These GenericTypes may then be used as parameters or return types within any method signatures defined on the interface. If a parameter or return type itself is a generic interface, the GenericTypes may be used as generic type arguments.
Implementing a generic interface will occur in a similar manner to regular interfaces, through the Interface Implementation Mapper dialog, by right clicking a Class and selecting Interface Mapping. The difference will be that if a generic interface is selected in this dialog, concrete Types must be selected to satisfy the interface's generic type parameters. Upon selecting the Implement button, a closed constructed generic interface will be instantiated and implemented on the selected class. This closed constructed generic interface is essentially a copy of the generic interface definition but its GenericType parameters and their usages have been replaced with the mapped concrete Types.
Generic interfaces may be used anywhere in method source that an existing non-generic interface can be used; however, any references in method source must be to a closed constructed variant of the generic interface. That is, the generic arguments specified must be concrete Types. The following shows example usages of a generic interface defined with the signature: CollectionIF<T>
method(collOfStrings: CollectionIF<String>): CollectionIF<Integer>;
feedMeInterfaces(CollectionIF<Integer>); // Method Parameter Usage
localVariable := Collection.firstInstance().CollectionIF<String>; // Type Guard Usage
interfaceMethod := CollectionIF<String>::add; // Meta Reference Usage
Similarly classes may have a reference to a generic interface, but it must be a closed constructed version. When a generic interface is selected in the Define Reference dialog, concrete types must be selected to satisfy the generic arguments.
In object-oriented languages and JADE specifically, it is possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign a Square object to a Shape object because Square is a kind of Shape (Shape is the super-class of Square). This also applies to method parameters.
square := create Square() transient;
shape := square; // Valid assignment
methodWithShapeParam(shape); // Valid parameter
methodWithShapeParam(square); // Also valid parameter as Square is a kind of Shape
Now consider the following method:
It can be seen that this method will accept an argument whose type is CollectionIF<Shape>, but the assumption that this method would also accept an argument whose type is CollectionIF<Square> would be wrong. This is because CollectionIF<Square> is not a subtype of CollectionIF<Shape>, as generic interfaces are invariant by default. The general rule is given two concrete types X and Y (for example Shape and Square), CollectionIF<X> has no relationship to CollectionIF<Y>, regardless of whether or not X and Y are related. The common supertype of CollectionIF<Shape> and CollectionIF<Square> is Object. However this subtype like relationship between two closed constructed versions of a generic interface when the generic type arguments are related can be achieved using covariance/contravariance, which will be described later.
Generic Interfaces can be subtyped by extending them with another generic interface. Imagine ArrayIF<T> extends CollectionIF<T>, therefore ArrayIF<Shape> is a subtype of CollectionIF<Shape>. As long as the generic type argument does not vary, the subtyping relationship exists. The collectionTest method from the previous example would then accept an argument whose type is ArrayIF<Shape> as it is a valid subtype.
As seen in the Generic Interface Extension and Subtyping section, generic type parameters are invariant by default. To achieve the subtype like relationship described, variant generic type parameters must be introduced. When creating a generic interface, there will be an option to set each generic type parameter as covariant or contravariant.
Covariance allows interface methods to have more derived return types than that defined by the generic type arguments. A generic type parameter can be marked as covariant when defining a generic interface but will have an added restriction that it can only be used as a return type in the interface’s method signatures or as a output parameter. An example of this is the new JadeIterableIF interface described later.
Contravariance allows interface methods to have less derived method parameter types than that defined by the generic type arguments. A generic type parameter can be marked as contravariant when defining a generic interface but will have an added restriction that it can only be used as a parameter type in the interface’s method signatures and cannot be used as a return type. An example of this is the new JadeComparerIF interface described later.
If a generic interface contains any covariant or contravariant generic type parameters, the generic interface definition will display the parameter followed by the output or input keyword respectively.
JadeIterableIF<T output> // Covariant generic type parameter T
- getIterator(): JadeIteratorIF<T>;
The equivalent to the .NET IEnumerable<T> or the Java Iterable<T> interfaces. JadeIterableIF<T> is an interface that defines one method getIterator(), which returns a JadeIteratorIF<T> interface providing read-only access to a collection.
Implemented by: RootSchema::Collection as JadeIterableIF<MemberType>
Due to the existing nature of Collection Classes in JADE, RootSchema::Collection will be implementing it using the pseudotype MemberType to satisfy the generic type parameter T. This will mean a Collection with membership of type Shape will effectively implement JadeIterableIF<Shape> and will be assignable to a variable of this type.
JadeIteratorIF<T output> // Covariant generic type parameter T
- next(value: T output): Boolean;
- current(value: T output): Boolean;
The equivalent to .NET IEnumerator<T> in .NET or the Java Enumerator<T> / Iterator<T> interfaces. Abstracts the current RootSchema::Iterator class exposing the Iterator::next(), Iterator::current() and Iterator::reset() methods.
An object that implements the JadeIteratorIf<T> interface generates a sequence of elements, one at a time. Successive calls to the next() method return successive elements of the sequence.
Consider defining interfaces for other collection types.
With the addition of the new JadeIterableIF<T> RootSchema interface, it will be possible to add compiler support to the foreach statement allowing the developer to iterate over any object that implements the JadeIterableIF<T> interface. This will allow iteration over real or virtual collections in a type-safe manner.
foreach shape in shapesCollection do
In this example, any class that implements JadeIterableIF<Shape> or due to the covariant nature of the generic type parameter, any class that implements JadeIterableIF<T> where T is a subtype of Shape (e.g. JadeIterable<Square>), will be accepted as a parameter to this method. This would also mean any Collection Class with membertype of Shape or subclass would be accepted e.g. ShapeArray, ShapeList, ShapeMemberKeyDict