Red Hat
May 24, 2011
by Gavin King

One of the best features of Ceylon is lexically-scoped introduction, which we discussed here, calling it decoration.

I've recently come to the conclusion that the explicit decorate statement is a barrier to modularity, and decided upon a slightly different approach. In this approach, an interface may declare that it adapts another type:

doc "Adaptor that introduces List to Sequence."
see (List,Sequence)
shared interface SequenceList<T> 
        adapts Sequence<T>
        satisfies List<T> {
    
    shared actual default List<T> sortedElements() {
    	//define the operation of List in
    	//terms of operations on Sequence
        return asList(sortSequence(this));
    }
    
    ...
    
}

Then the interface is called an introduction - or, alternatively, an adapter, in a nod to the terminology used in the Go4 book. (In that book, a decorator is a slightly different concept.) According to the spec:

The interface may not:
  • declare or inherit a member that refines a member of any adapted type, or
  • declare or inherit a formal or non-default actual member unless the member is inherited from an adapted type.

Now, to enable the introduction in a certain compilation unit, all you need to do is import it.

import ceylon.collection { SequenceList }    //import the adapter

String[] cities = { "Melbourne", "Atlanta", "San Francisco", "Guanajuato", "Paris" };
List<String> sortedCities = cities.sortedElements();    //call the adapter

Again, according to the spec:

If, in a certain compilation unit, multiple introductions of a certain adapted type declare or inherit a member that refines a common member of a common supertype then either:
  • there must be a unique member from the set of members, called the most refined member, that refines all the other members, or
  • the adapted type must declare or inherit a member that refines all the members.
At runtime, an operation (method invocation, member class instantiation, or attribute evaluation) upon any type that is a subtype of all the adapted types is dispatched according to the following rule:
  • If the runtime type of the instance of the adapted type declares or inherits a member defining the operation, the operation is dispatched to the runtime type of the instance.
  • Otherwise, the operation is dispatched to the introduction that has the most-refined member defining the operation.

I think this is a significantly better approach, making introduction much easier to use.

UPDATE: A commenter asks if Ceylon will support C#-style extension methods. No need. Every concrete method of an adapter is an extension method! Indeed, an adapter without a satisfies clause is just a package of extension methods and attributes.