Reputation: 6611
This Scala tutorial is confusing to me; the Node
abstract type doesn't seem to following traditional rules of polymorphism...
type Node <: NodeIntf // NodeIntf is assignable to Node.
abstract class NodeIntf {
def connectWith(node: Node): Edge
}
class NodeImpl extends NodeIntf {
def connectWith(node: Node): Edge = {
val edge = newEdge(this, node) // NodeImpl (this) is assignable to NodeIntf.
edges = edge :: edges
edge
}
}
protected def newEdge(from: Node, to: Node): Edge
If Node = NodeIntf
and NodeIntf = NodeImpl
, then why can't we do Node = NodeImpl
? I ask because apparently the above code won't compile - why must a 'self typed reference' be used? (see tutorial)
Upvotes: 2
Views: 197
Reputation: 12852
Firstly, here is a sort-of minimal, self-contained version of your code:
abstract class Graph {
type Node <: NodeIntf
case class Edge(s: Node, d: Node)
abstract class NodeIntf {
def connectWith(node: Node): Edge
}
class NodeImpl extends NodeIntf {
def connectWith(node: Node): Edge = {
val edge = newEdge(this, node)
edge
}
}
def newEdge(from: Node, to: Node): Edge = Edge(from, to)
}
If you try to compile it, you'll get
found : NodeImpl.this.type (with underlying type Graph.this.NodeImpl)
required: Graph.this.Node
val edge = newEdge(this, node)
^
The reason for the error message is, that Node
is an abstract type. It has the upper bound NodeIntf
, but it is nevertheless still abstract. That is, an implementation of the abstract Graph
is free to set/bind Node
to any subtype of NodeIntf
.
In your code, you try to pass an instance of NodeImpl
to newEdge
, which expects a Node
. You are right in that NodeImpl
is a subtype of NodeIntf
, however, an implementation of Graph
might decide to restrict Node
even further by binding it to a subtype of NodeImpl
, which would make your call to newEdge
illegal.
If you already bind Node
, e.g., by making it a type alias of NodeIntf
,
type Node = NodeIntf
then the above code compiles because later implementations of Graph
cannot bind Node
anymore.
Upvotes: 3
Reputation: 297265
You inverted the meaning of <:
. A Node
is assignable to a NodeIntf
, that is:
val x: NodeIntf = y: Node
Now, further below you say Node = NodeIntf
and NodeIntf = NodeImpl
, which is not true. Node
is an arbitrary subtype of NodeIntf
, and NodeImpl
is a specific subtype of NodeIntf
.
In terms of is a, Node
is a NodeIntf
, and NodeImpl
is a NodeIntf
, but that has no meaning on the relationship between them -- you might as well have said that both Node
and NodeImpl
are both subtypes of Any
.
Upvotes: 7