Skip to content

Add code tabs for _tour/inner-classes #2550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 22, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions _tour/inner-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 %}