|
| 1 | +--- |
| 2 | +layout: overview |
| 3 | +title: Explicitly Typed Self References |
| 4 | +--- |
| 5 | + |
| 6 | +When developing extensible software it is sometimes handy to declare the type of the value `this` explicitly. To motivate this, we will derive a small extensible representation of a graph data structure in Scala. |
| 7 | + |
| 8 | +Here is a definition describing graphs: |
| 9 | + |
| 10 | + abstract class Graph { |
| 11 | + type Edge |
| 12 | + type Node <: NodeIntf |
| 13 | + abstract class NodeIntf { |
| 14 | + def connectWith(node: Node): Edge |
| 15 | + } |
| 16 | + def nodes: List[Node] |
| 17 | + def edges: List[Edge] |
| 18 | + def addNode: Node |
| 19 | + } |
| 20 | + |
| 21 | +Graphs consist of a list of nodes and edges where both the node and the edge type are left abstract. The use of [abstract types](abstract-types.html) allows implementations of trait Graph to provide their own concrete classes for nodes and edges. Furthermore, there is a method `addNode` for adding new nodes to a graph. Nodes are connected using `methodconnectWith`. |
| 22 | + |
| 23 | +A possible implementation of class `Graph` is given in the next program: |
| 24 | + |
| 25 | + abstract class DirectedGraph extends Graph { |
| 26 | + type Edge <: EdgeImpl |
| 27 | + class EdgeImpl(origin: Node, dest: Node) { |
| 28 | + def from = origin |
| 29 | + def to = dest |
| 30 | + } |
| 31 | + class NodeImpl extends NodeIntf { |
| 32 | + def connectWith(node: Node): Edge = { |
| 33 | + val edge = newEdge(this, node) |
| 34 | + edges = edge :: edges |
| 35 | + edge |
| 36 | + } |
| 37 | + } |
| 38 | + protected def newNode: Node |
| 39 | + protected def newEdge(from: Node, to: Node): Edge |
| 40 | + var nodes: List[Node] = Nil |
| 41 | + var edges: List[Edge] = Nil |
| 42 | + def addNode: Node = { |
| 43 | + val node = newNode |
| 44 | + nodes = node :: nodes |
| 45 | + node |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | +Class `DirectedGraph` specializes the `Graph` class by providing a partial implementation. The implementation is only partial, because we would like to be able to extend `DirectedGraph` further. Therefore this class leaves all implementation details open and thus both the edge and the node type are left abstract. Nevertheless, class `DirectedGraph` reveals some additional details about the implementation of the edge type by tightening the bound to class `EdgeImpl`. Furthermore, we have some preliminary implementations of edges and nodes represented by the classes `EdgeImpl` and `NodeImpl`. Since it is necessary to create new node and edge objects within our partial graph implementation, we also have to add the factory methods `newNode` and `newEdge`. The methods `addNode` and `connectWith` are both defined in terms of these factory methods. A closer look at the implementation of method `connectWith` reveals that for creating an edge, we have to pass the self reference `this` to the factory method `newEdge`. But this is assigned the type `NodeImpl`, so it's not compatible with type `Node` which is required by the corresponding factory method. As a consequence, the program above is not well-formed and the Scala compiler will issue an error message. |
| 50 | + |
| 51 | +In Scala it is possible to tie a class to another type (which will be implemented in future) by giving self reference thisthe other type explicitly. We can use this mechanism for fixing our code above. The explicit self type is specified within the body of the class `DirectedGraph`. |
| 52 | + |
| 53 | +Here is the fixed program: |
| 54 | + |
| 55 | + abstract class DirectedGraph extends Graph { |
| 56 | + ... |
| 57 | + class NodeImpl extends NodeIntf { |
| 58 | + self: Node => |
| 59 | + def connectWith(node: Node): Edge = { |
| 60 | + val edge = newEdge(this, node) // now legal |
| 61 | + edges = edge :: edges |
| 62 | + edge |
| 63 | + } |
| 64 | + } |
| 65 | + ... |
| 66 | + } |
| 67 | + |
| 68 | +In this new definition of class `NodeImpl`, `this` has type `Node`. Since type `Node` is abstract and we therefore don't know yet if `NodeImpl` is really a subtype of `Node`, the type system of Scala will not allow us to instantiate this class. But nevertheless, we state with the explicit type annotation of this that at some point, (a subclass of) `NodeImpl` has to denote a subtype of type `Node` in order to be instantiatable. |
| 69 | + |
| 70 | +Here is a concrete specialization of `DirectedGraph` where all abstract class members are turned into concrete ones: |
| 71 | + |
| 72 | + class ConcreteDirectedGraph extends DirectedGraph { |
| 73 | + type Edge = EdgeImpl |
| 74 | + type Node = NodeImpl |
| 75 | + protected def newNode: Node = new NodeImpl |
| 76 | + protected def newEdge(f: Node, t: Node): Edge = |
| 77 | + new EdgeImpl(f, t) |
| 78 | + } |
| 79 | + |
| 80 | +Please note that in this class, we can instantiate `NodeImpl` because now we know that `NodeImpl` denotes a subtype of type `Node` (which is simply an alias for `NodeImpl`). |
| 81 | + |
| 82 | +Here is a usage example of class `ConcreteDirectedGraph`: |
| 83 | + |
| 84 | + object GraphTest extends Application { |
| 85 | + val g: Graph = new ConcreteDirectedGraph |
| 86 | + val n1 = g.addNode |
| 87 | + val n2 = g.addNode |
| 88 | + val n3 = g.addNode |
| 89 | + n1.connectWith(n2) |
| 90 | + n2.connectWith(n3) |
| 91 | + n1.connectWith(n3) |
| 92 | + } |
| 93 | + |
0 commit comments