From f963d863c3f3db3faab39ab55f0a6b4812df2201 Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Thu, 30 Mar 2017 14:36:00 +0200 Subject: [PATCH] Rewrote inner classes tour section --- .../tour/_posts/2017-02-13-inner-classes.md | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/tutorials/tour/_posts/2017-02-13-inner-classes.md b/tutorials/tour/_posts/2017-02-13-inner-classes.md index 618a91e3f4..15b0307964 100644 --- a/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ b/tutorials/tour/_posts/2017-02-13-inner-classes.md @@ -11,8 +11,10 @@ next-page: abstract-types previous-page: lower-type-bounds --- -In Scala it is possible to let classes have other classes as members. Opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object. To illustrate the difference, we quickly sketch the implementation of a graph datatype: - +In Scala it is possible to let classes have other classes as members. As opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object. Suppose we want the compiler to prevent us, at compile time, from mixing up which nodes belong to what graph. Path-dependent types provide a solution. + +To illustrate the difference, we quickly sketch the implementation of a graph datatype: + ```tut class Graph { class Node { @@ -31,50 +33,32 @@ class Graph { } } ``` - -In our program, graphs are represented by a list of nodes. Nodes are objects of inner class `Node`. Each node has a list of neighbours, which get stored in the list `connectedNodes`. Now we can set up a graph with some nodes and connect the nodes incrementally: - -```tut -object GraphTest extends App { - val g = new Graph - val n1 = g.newNode - val n2 = g.newNode - val n3 = g.newNode - n1.connectTo(n2) - n3.connectTo(n1) -} -``` - -We now enrich the above example with types to state explicitly what the type of the various defined entities is: - +This program represents a graph as a list of nodes (`List[Node]`). Each node has a list of other nodes it's connected to (`connectedNodes`). The `class Node` is a _path-dependent type_ because it is nested in the `class Graph`. Therefore, all nodes in the `connectedNodes` must be created using the `newNode` from the same instance of `Graph`. + ```tut -object GraphTest extends App { - val g: Graph = new Graph - val n1: g.Node = g.newNode - val n2: g.Node = g.newNode - val n3: g.Node = g.newNode - n1.connectTo(n2) - n3.connectTo(n1) -} +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +val node3: graph1.Node = graph1.newNode +node1.connectTo(node2) +node3.connectTo(node1) ``` - -This code clearly shows that a node type is prefixed with its outer instance (which is object `g` in our example). If we now have two graphs, the type system of Scala does not allow us to mix nodes defined within one graph with the nodes of another graph, since the nodes of the other graph have a different type. +We have explicitly declared the type of `node1`, `node2`, and `node3` as `graph1.Node` for clarity but the compiler could have inferred it. This is because when we call `graph1.newNode` which calls `new Node`, the method is using the instance of `Node` specific to the instance `graph1`. + +If we now have two graphs, the type system of Scala does not allow us to mix nodes defined within one graph with the nodes of another graph, since the nodes of the other graph have a different type. Here is an illegal program: - -```tut:fail -object IllegalGraphTest extends App { - val g: Graph = new Graph - val n1: g.Node = g.newNode - val n2: g.Node = g.newNode - n1.connectTo(n2) // legal - val h: Graph = new Graph - val n3: h.Node = h.newNode - n1.connectTo(n3) // illegal! -} + ``` - -Please note that in Java the last line in the previous example program would have been correct. For nodes of both graphs, Java would assign the same type `Graph.Node`; i.e. `Node` is prefixed with class `Graph`. In Scala such a type can be expressed as well, it is written `Graph#Node`. If we want to be able to connect nodes of different graphs, we have to change the definition of our initial graph implementation in the following way: - +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +node1.connectTo(node2) // legal +val graph2: Graph = new Graph +val node3: graph2.Node = graph2.newNode +node1.connectTo(node3) // illegal! +``` +The type `graph1.Node` is distinct from the type `graph2.Node`. In Java, the last line in the previous example program would have been correct. For nodes of both graphs, Java would assign the same type `Graph.Node`; i.e. `Node` is prefixed with class `Graph`. In Scala such a type can be expressed as well, it is written `Graph#Node`. If we want to be able to connect nodes of different graphs, we have to change the definition of our initial graph implementation in the following way: + ```tut class Graph { class Node { @@ -93,5 +77,5 @@ class Graph { } } ``` - -> Please note that this program doesn't allow us to attach a node to two different graphs. If we want to remove this restriction as well, we have to change the type of variable nodes to `Graph#Node`. + +> Note that this program doesn't allow us to attach a node to two different graphs. If we want to remove this restriction as well, we have to change the type of variable nodes to `Graph#Node`.