From af38c491ef562544688562bc455b8193ee3f8d1a Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 22 Sep 2022 15:47:48 +0200 Subject: [PATCH] Add code tabs for _tour/inner-classes --- _tour/inner-classes.md | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/_tour/inner-classes.md b/_tour/inner-classes.md index fc9e4f150a..e2d94542b4 100644 --- a/_tour/inner-classes.md +++ b/_tour/inner-classes.md @@ -14,6 +14,8 @@ In Scala it is possible to let classes have other classes as members. As opposed To illustrate the difference, we quickly sketch the implementation of a graph datatype: +{% tabs inner-classes_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_1 %} ```scala mdoc class Graph { class Node { @@ -32,8 +34,29 @@ class Graph { } } ``` +{% endtab %} +{% tab 'Scala 3' for=inner-classes_1 %} +```scala +class Graph: + class Node: + var connectedNodes: List[Node] = Nil + def connectTo(node: Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` +{% endtab %} +{% endtabs %} + 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`. +{% tabs inner-classes_2 %} +{% tab 'Scala 2 and 3' for=inner-classes_2 %} ```scala mdoc val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -42,11 +65,16 @@ val node3: graph1.Node = graph1.newNode node1.connectTo(node2) node3.connectTo(node1) ``` +{% endtab %} +{% endtabs %} + 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: +{% tabs inner-classes_3 %} +{% tab 'Scala 2 and 3' for=inner-classes_3 %} ```scala mdoc:fail val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -56,8 +84,13 @@ val graph2: Graph = new Graph val node3: graph2.Node = graph2.newNode node1.connectTo(node3) // illegal! ``` +{% endtab %} +{% endtabs %} + 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: +{% tabs inner-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_4 %} ```scala mdoc:nest class Graph { class Node { @@ -76,3 +109,21 @@ class Graph { } } ``` +{% endtab %} +{% tab 'Scala 3' for=inner-classes_4 %} +```scala +class Graph: + class Node: + var connectedNodes: List[Graph#Node] = Nil + def connectTo(node: Graph#Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` +{% endtab %} +{% endtabs %}