Lawrence Wagerfield
Lawrence Wagerfield

Reputation: 6611

Scala Abstract Types and Polymorphism

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

Answers (2)

Malte Schwerhoff
Malte Schwerhoff

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

Daniel C. Sobral
Daniel C. Sobral

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

Related Questions