Scala ひと巡り : 内部クラス (Inner Classes)

Scala では、クラスは他のクラスをメンバーとして持てます。Java ライクな言語では、そのような内部クラスは取り囲むクラスのメンバーですが、Scala では反対に、そのような内部クラスは外側のオブジェクトへ束縛されます。違いを明らかにするために、グラフデータ型の実装概要を示します:

class Graph { class Node { var connectedNodes: List[Node] = Nil def connectTo(node: Node) { if (connectedNodes.find(node.equals).isEmpty) { connectedNodes = node :: connectedNodes } } } var nodes: List[Node] = Nil def newNode: Node = { val res = new Node nodes = res :: nodes res } }

このプログラムで、グラフはノードのリストによって表されます。ノードは内部クラス Node のオブジェクトです。各ノードは 隣のリストを持っており、それはリスト connectedNodes 中に記憶されます。これでいくつかのノードをもち、ノードを付加的に(incrementally)連結できるグラフをセットアップできます。

object GraphTest extends Application { val g = new Graph val n1 = g.newNode val n2 = g.newNode val n3 = g.newNode n1.connectTo(n2) n3.connectTo(n1) }

次に、定義されるエンティティの型が何であるかを明示する型を用いて、例を補強します:

object GraphTest extends Application { 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) }

このコードは、ノードの型が、その外側のインスタンス(この例ではオブジェクト g )で前置されることを明らかにしています。今 2 つのグラフがあるとして、Scala の型システムは、一方のグラフ内で定義されたノードと他方のグラフのノードを混ぜることを許しません。なぜなら、他のグラフのノードは異なる型をもつから です。

次は不正なプログラムです:

object IllegalGraphTest extends Application { val g: Graph = new Graph val n1: g.Node = g.newNode val n2: g.Node = g.newNode n1.connectTo(n2) // 正しい val h: Graph = new Graph val n3: h.Node = h.newNode n1.connectTo(n3) // 不正! }

Javaでは、上記サンプルプログラムの最後の行は正しいことに注意してください。Java は両方のグラフのノードに同じ型 Graph.Node を割り当てます。すなわち、Node はクラス Graph で前置されます(*1)。 Scalaでは、そのような型も表現でき、Graph#Node と書きます。もし異なるグラフのノードを連結できるようにしたければ、最初のグラフ実装の定義を次のように変える必要があります。

class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil def connectTo(node: Graph#Node) { if (connectedNodes.find(node.equals).isEmpty) { connectedNodes = node :: connectedNodes } } } var nodes: List[Node] = Nil def newNode: Node = { val res = new Node nodes = res :: nodes res } }

このプログラムが、2 つの異なるグラフへのノードの張りつけを許さないことに注意してください。もしこの制限もなくしたければ、変数 nodes の型とメソッド newNode の戻り値型を Graph#Node に変える必要があります。

(*1)訳注: Scalaではクラス Graph ではなくオブジェクト g が前置されていて、 g.Node と h.Node はパスが異なるので異なる型を表すということ。