在 Scala 中,可以讓類別將其他類別設為成員。與 Java 類似的語言中,此類內部類別是封閉類別的成員,但在 Scala 中,此類內部類別會繫結到外部物件。假設我們希望編譯器在編譯時防止我們將哪些節點混淆到哪些圖形中。路徑依賴型別提供了解決方案。
為了說明差異,我們快速勾勒出圖形資料類型的實作
class Graph {
class Node {
var connectedNodes: List[Node] = Nil
def connectTo(node: Node): Unit = {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
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
此程式將圖形表示為節點清單 (List[Node]
)。每個節點都有其連接的其他節點清單 (connectedNodes
)。class Node
是路徑依賴型別,因為它嵌套在 class Graph
中。因此,connectedNodes
中的所有節點都必須使用 Graph
的相同執行個體中的 newNode
建立。
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)
我們明確宣告 node1
、node2
和 node3
的類型為 graph1.Node
以求清晰,但編譯器也能推論出來。這是因為當我們呼叫 graph1.newNode
,而它呼叫 new Node
時,此方法使用特定於 graph1
這個實例的 Node
實例。
如果我們現在有兩個圖形,Scala 的類型系統不允許我們將定義在一個圖形中的節點與另一個圖形的節點混合,因為另一個圖形的節點有不同的類型。以下是個非法程式
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!
類型 graph1.Node
與類型 graph2.Node
是不同的。在 Java 中,前一個範例程式中的最後一行會是正確的。對於兩個圖形的節點,Java 會指定相同的類型 Graph.Node
;亦即 Node
前面加上類別 Graph
。在 Scala 中,這樣的類型也可以表達,寫法為 Graph#Node
。如果我們想要連接不同圖形的節點,就必須以以下方式變更我們的初始圖形實作定義
class Graph {
class Node {
var connectedNodes: List[Graph#Node] = Nil
def connectTo(node: Graph#Node): Unit = {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
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