JADE Environment Development Ideas

What's new in the upcoming JADE release?

IMPROVED DEVELOPER EFFICIENCY. ENHANCED SECURITY. SMOOTHER INTEGRATION

The JADE 2022 release meets tomorrow’s business demands.


Start your update to JADE's latest release

Generic Interfaces

Overview

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.

Feature Summary

The proposed feature set will include:

  • Support for Generic Interfaces

  • New RootSchema Generic Interfaces

  • Extension to foreach statements, to support custom collections

Generic Interface Definition

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.

Generic Interface Implementation

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 Interface Usages

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>;
vars
localVariable: CollectionIF<String>;
interfaceMethod: JadeInterfaceMethod;
begin
feedMeInterfaces(CollectionIF<Integer>); // Method Parameter Usage
localVariable := Collection.firstInstance().CollectionIF<String>; // Type Guard Usage
interfaceMethod := CollectionIF<String>::add; // Meta Reference Usage
end;

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.

Generic Interface Extension and Subtyping

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.

vars
square: Square;
shape: Shape;
begin
square := create Square() transient;
shape := square; // Valid assignment

methodWithShapeParam(shape); // Valid parameter
methodWithShapeParam(square); // Also valid parameter as Square is a kind of Shape
end;

Now consider the following method:

collectionTest(collOfShapes: CollectionIF<Shape>);

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.

Generic Interface Variance

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.

New RootSchema Generic Interfaces

JadeIterableIF<T output> Interface

Definition
JadeIterableIF<T output> // Covariant generic type parameter T
- getIterator(): JadeIteratorIF<T>;
Summary

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> Interface

Definition
JadeIteratorIF<T output> // Covariant generic type parameter T
- next(value: T output): Boolean;
- current(value: T output): Boolean;
Summary

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.

Further Interfaces

Consider defining interfaces for other collection types.

Foreach support for JadeIterableIF<T> interfaces

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.

Example
iterateShapes(shapesCollection: JadeIterableIF<Shape>);
vars
shape: Shape;
begin
foreach shape in shapesCollection do
write shape.numberOfSides;
endforeach;
end;

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

  • Ashley Bass
  • Aug 8 2019
  • Future consideration: 2022
  • Attach files
  • Kevin Saul commented
    August 30, 2019 00:31

    Yep, any custom collection should always create a new iterator as well, rather than return an existing which may still be in use, etc.

     

    Okay, having a JadeReverseIteratorIF<T> interface would work, which would presumably be a subtype of JadeIteratorIF<T>?

     

    Then there'd need to be a complimentary JadeReverseIterableIF<T> interface (presumably a subtype of JadeIterableIF<T>), which the compiler would also support for the foreach reverse option to work?

  • Mathew Hylkema commented
    August 29, 2019 23:49

    Hi Kevin,

    The Collection implementation of JadeIterableIF<T>::getIterator() will likely wrap the existing Collection::createIterator() method. Seems reasonable to name it accordingly, I will make a note about this. Obviously if you are implementing the interface on a custom 'collection' class you are responsible for implementing it accordingly. 

    At this stage JadeIteratorIF<T> will not provide a 'back' method, as we want it to be as generic as possible. We could also provide a reverse iterator interface, but JadeIterableIF<T>::getIterator() will return the forward iterator. 

  • Kevin Saul commented
    August 29, 2019 23:01

    Will JadeIteratorIF<T> also provide a 'back' method for iterating in reverse?

  • Kevin Saul commented
    August 29, 2019 22:56

    This looks great!

     

    Will JadeIterableIF<T>::getIterator() return an existing iterator or create a new one?  If it's the latter, can this please be called createIterator so it's clear the owner is responsible for clean-up, same as Collection::createIterator.