Programação Multiparadigma (2014/2015)



Teórica 08 (27/Out/2014)

Abstract types.
Path-dependent types.
Explicitly Typed Self References.
Family polymorphism.

Abstract types

This presentation is based on the bibliographic reference [D].

We start by discussing parametrized types again, and then we will contrast them with the new concept of abstract types. These two features partially overlap. Nevertheless, each feature is better adjusted to different design problems. Parametrized types are the best for the definition of collections; abstract types are the best for the definition of type families and other complex type scenarios.

In the bibliographic reference [D], the abstraction corresponding to the parametrized types is called functional abstraction, and the abstraction corresponding to abstract types is called object-oriented abstraction.

Parametrized types (functional abstraction)

One example: Using a parametrized type, a generic cell type can be defined as follows: We can instantiate the parametrized class to produce another, more specific, class: Here is an example of creation: We can also directly generate a compatible singleton:

Abstract types (object-oriented abstraction)

Warning: abstract types is not the same as abstract classes neither the same as abstract data types.

Using abstract types, the generic cell type is defined as follows:

We can instantiate the parametrized class to produce another, more specific class: Here is an example of creation: We can also directly generate a compatible singleton: In the previous type we say that the type AbsCell was augmented with the refinement {type T = Int}.

For proper support of abstract types, Scala needs to add some new mechanism that are discussed next.

Path-dependent types

The following interesting polymorphic function, implicitly abstract on T, works for all the instantiations of T: This function is safe because c.init has type c.T and c.set has type (c.T)Unit.

c.T is one example of a path-dependent type. In general, such a type has the form x1.x2.x3. ... .xn.T, where the prefix x1.x2.x3. ... .xn is a sequence of immutable values and T is a type member of xn.

Explicitly Typed Self References

Inside a class, it is possible to declare the type of this explicitly. We need this feature, when defining certain abstract classes with a partial implementation. The following example is based on an example from the Scala documentation: here.

Here is an abstract definition of graphs. The use of abstract types allows implementations of the abstract class to provide their own concrete classes for nodes and edges.

We want to use our graphs as in the following example: Now, here is a concrete implementation of directed graphs. The protected methods newNode and newEdge were introduced to allow further refinements in subclasses, as the the types Edge and Node are already frozen. Now, we try to develop an alternative, less concrete implementation of directed graphs. The following abstract class commits itself with many implementation details, however it still leaves some implementation details open to the point that both the edge and the node type are left abstract. But this code is invalid. The following compiler error is issued: This problem is that newEdge is expecting a Node at the first position but is called with a NodeImpl instead... and NodeImpl is not a subtype of Node.

The solution if to insert this: Node => at the beginning of the class NodeImpl to obtain:

Now, inside the class NodeImpl, this has type Node. With the explicitly typed self reference we state that at some point, the class NodeImpl (or a subclass) has to denote a subtype of Node in order to be instantiatable.

Now, here is the simplest fully concrete implementation of the abstract class DirectedGraph:

"Self-types can be arbitrary; they need not have a relation with the class being defined. Type soundness is still guaranteed, because of two requirements: (1) the self-type of a class must be a subtype of the self-types of all its base classes, (2) when instantiating a class in a new expression, it is checked that the self type of the class is a supertype of the type of the object being created."


Family polymorphism

This example is also from the bibliographic reference [D].

The object-oriented abstraction mechanism of Scala provides an excellent solution for an usually hard to implement concept of family polymorphism. A program exhibits family polymorphism when there is a family of types that vary together covariantly, along parallel hierarchies.

The following example is adapted from the Scala documentation.

We are going to discuss an implementation in Scala of the publish/subscribe design pattern, the same design pattern that is used is JavaBeans and the Swing library of Java.

Subject: Define a method subscribe that a observer use to register on a particular subject. Also define method publish to notify the observers of a subject of an event, topically when the internal state of the subject changes.

Observer: Define a method notify that is used by the subjects to notify the observer.

A subscribe method takes the registering observer as parameter, whereas an notify method takes the subject that did the notification as parameter. Hence, subjects and observers refer to each other in their method signatures. All elements of this design pattern are captured in the following system.

The definition of the pattern abstract on the types of the subjects and the observers, to allow covariant extensions of these classes in client code. Why is an explicitly typed self reference needed here?

Now a concrete instance of the design pattern. Finally, one example of use:

#8