在Scala中混合使用类型参数和抽象类型

9
我正在尝试使用之前的问题的答案来实现一个小型图形库。想法是将图形视为集合,其中顶点包装集合元素。
我希望使用抽象类型来表示顶点和边缘类型(因为需要类型安全),并且我想使用类型参数来表示集合元素的类型(因为我希望在实例化时轻松定义它们)。
但是,当我尝试最基本的示例时,我遇到了编译错误。以下是示例:
package graph

abstract class GraphKind[T] {

  type V <: Vertex[T]
  type G <: Graph[T]

  def newGraph(): G

  abstract class Graph[T] extends Collection[T]{
    self: G =>
    def vertices(): List[V]
    def add(t: T): Unit
    def size(): Int
    def elements(): Iterator[T]
  }

  trait Vertex[T] {
    self: V =>
      def graph(): G
      def value(): T
  }

}

以下是基本实现:

class SimpleGraphKind[T] extends GraphKind[T] {

  type G = GraphImpl[T]
  type V = VertexImpl[T]

  def newGraph() = new GraphImpl[T]

  class GraphImpl[T] extends Graph[T] {
    private var vertices_ = List[V]()
    def vertices = vertices_
    def add( t: T ) {  vertices_ ::= new VertexImpl[T](t,this) }
    def size() = vertices_.size
    def elements() = vertices.map( _.value ).elements
  }

  class VertexImpl[T](val value: T, val graph: GraphImpl[T]) extends Vertex[T] {
    override lazy val toString = "Vertex(" + value.toString + ")"
  }

}

编译时,我遇到了以下问题:

/prg/ScalaGraph/study/Graph.scala:10: error: illegal inheritance;
 self-type GraphKind.this.G does not conform to Collection[T]'s selftype Collection[T]
  abstract class Graph[T] extends Collection[T]{
                              ^
/prg/ScalaGraph/study/Graph.scala:33: error: illegal inheritance;
 self-type SimpleGraphKind.this.GraphImpl[T] does not conform to   SimpleGraphKind.this.Graph[T]'s selftype SimpleGraphKind.this.G
  class GraphImpl[T] extends Graph[T] {
                         ^
/prg/ScalaGraph/study/Graph.scala:36: error: type mismatch;
 found   : SimpleGraphKind.this.VertexImpl[T]
 required: SimpleGraphKind.this.V
    def add( t: T ) {  vertices_ ::= new VertexImpl[T](t,this) }
                                 ^
/prg/ScalaGraph/study/Graph.scala:38: error: type mismatch;
 found   : Iterator[T(in class SimpleGraphKind)]
 required: Iterator[T(in class GraphImpl)]
    def elements() = vertices.map( _.value ).elements
                                         ^
/prg/ScalaGraph/study/Graph.scala:41: error: illegal inheritance;
 self-type SimpleGraphKind.this.VertexImpl[T] does not conform to   SimpleGraphKind.this.Vertex[T]'s selftype SimpleGraphKind.this.V
  class VertexImpl[T](val value: T, val graph: GraphImpl[T]) extends Vertex[T] {
                                                                 ^
5 errors found

我完全不知道这些错误的含义... 但是,如果我在实现中专门指定类型T(class SimpleGraphKind extends GraphKind [Int] ),我只会得到第一个错误。

你有什么想法吗?


你能解释一下为什么你想要一个图形成为一个集合吗? - Randall Schulz
这个库的一个应用是在图上实现一种细胞自动机(另一个应用是复杂网络研究)。然后,直接访问封装在顶点中的单元对象可能会很好...但如果您有解决方案的想法,而不需要将图形作为集合功能,我也很感兴趣。 - paradigmatic
我仍然不确定我看到联系了。您如何看待您的图形库与JGraphT有所区别?它是一种面向图形的功能方法吗? - Randall Schulz
JGraphT(或Jung)不限制顶点和边的类型。因此,您必须依赖于图对象来执行图操作。相比之下,我发现直接操作边和顶点对象更加自然(例如,我更喜欢vertex1.connectTo(vertex2)vertex1 -> vertex2而不是graph.connect(vertex1, vertex2))。顶点可以包含逻辑,也可以只是图对象的代理,具体取决于实现方式。 - paradigmatic
2个回答

11

使用-explaintypes编译会产生以下结果:

<console>:11: error: illegal inheritance;
 self-type GraphKind.this.G does not conform to Collection[T]'s selftype Collection[T]
         abstract class Graph[T] extends Collection[T]{
                                         ^
    GraphKind.this.G <: Collection[T]?
      Iterable[T] <: Iterable[T]?
        T <: T?
          T <: Nothing?
            <notype> <: Nothing?
            false
            Any <: Nothing?
              <notype> <: Nothing?
              false
            false
          false
          Any <: T?
            Any <: Nothing?
              <notype> <: Nothing?
              false
            false
          false
        false
      false
      GraphKind.this.Graph[T] <: Iterable[T]?
        Iterable[T] <: Iterable[T]?
          T <: T?
            T <: Nothing?
              <notype> <: Nothing?
              false
              Any <: Nothing?
                <notype> <: Nothing?
                false
              false
            false
            Any <: T?
              Any <: Nothing?
                <notype> <: Nothing?
                false
              false
            false
          false
        false
      false
    false

现在,我正要写下我不明白为什么T <: T会是假的——这几乎就像T被定义了两次,这正是问题所在。这里:

abstract class GraphKind[T] { 

  type V <: Vertex[T] 
  type G <: Graph[T] 

  def newGraph(): G 

  abstract class Graph[T] extends Collection[T]{ 

好的,类GraphKind是使用T进行参数化的,并且类型G必须是Graph[T]。现在,类Graph也是参数化的,其参数也称为T。为了防止混淆,让我们进行重写:

  abstract class Graph[T2] extends Collection[T2]{
    self: G =>
    def vertices(): List[V]
    def add(t: T2): Unit
    def size(): Int
    def elements(): Iterator[T2]
  }

请注意,这与您所写的完全相同。我只是使用不同的类型参数名称,以便它不会与为GraphKind参数化的T混淆。
因此,这里是逻辑:
G <: Graph[T]
Graph[T2] <: Collection[T2]
Graph[T2] <: G  // self type

这意味着

Graph[T2] <: Graph[T]

因为Graph扩展了Collection,所以:

Collection[T2] <: Collection[T]

但是并不能保证这是正确的。我不明白为什么当没有继承时问题不会出现。修复方法:

abstract class GraphKind[T] {

  type V <: Vertex
  type G <: Graph

  def newGraph(): G

  abstract class Graph extends Collection[T]{
    self: G =>
    def vertices(): List[V]
    def add(t: T): Unit
    def size(): Int
    def elements(): Iterator[T]
  }

  trait Vertex {
    self: V =>
      def graph(): G
      def value(): T
  }

}

class SimpleGraphKind[T] extends GraphKind[T] {

  type G = GraphImpl
  type V = VertexImpl

  def newGraph() = new GraphImpl

  class GraphImpl extends Graph {
    private var vertices_ = List[V]()
    def vertices = vertices_
    def add( t: T ) {  vertices_ ::= new VertexImpl(t,this) }
    override def size() = vertices_.size
    override def elements() = vertices.map( _.value ).elements
  }

  class VertexImpl(val value: T, val graph: GraphImpl) extends Vertex {
    override lazy val toString = "Vertex(" + value.toString + ")"
  }
}

由于VertexGraph将与GraphKind的一个实例相关联,因此T将被固定为该实例定义的任何内容。例如:

scala> new SimpleGraphKind[Int]
res0: SimpleGraphKind[Int] = SimpleGraphKind@1dd0fe7

scala> new res0.GraphImpl
res1: res0.GraphImpl = line10()

scala> res1.add(10)

scala> res1.add("abc")
<console>:9: error: type mismatch;
 found   : java.lang.String("abc")
 required: Int
       res1.add("abc")
                ^

非常感谢。我必须承认我感到羞愧,因为5年前学习Java泛型时犯了类似的错误... - paradigmatic
1
不需要感到羞愧。我也这样做,然后讨厌自己这样做。并不是我不知道它的工作原理,只是自然而然地写出 D[T] extends C[T] - Daniel C. Sobral
另一方面,-explaintypes 是你的好朋友。需要一些时间来适应它,特别是当类型从协变位置跳转到反变位置时,类型会发生变化。然而,在处理复杂类型错误时,没有什么比它更好的了。 - Daniel C. Sobral

1

看起来,Vertex 属于特定的图形并且只能使用嵌套 Vertex trait 在 Graph 中表示最佳。您尝试实现的目标是否可以通过以下结构实现?

abstract class Graph[T] extends Collection[T] {
  thisGraph: Graph[T] =>

  def newGraph: Graph[T] 
  def vertices: List[Vertex[T]]
  def add(t: T): Unit
  def size: Int
  def elements: Iterator[T]

  trait Vertex[T] {
      def graph = thisGraph
      def value: T
  }
}

class SimpleGraph[T] extends Graph[T] {
  private[this] var verts = List[Vertex[T]]()

  def newGraph = new SimpleGraph[T]
  def vertices = verts
  def add(t: T) { verts ::= SimpleVertex(t) }
  override def size = verts.size
  override def elements = verts.map(_.value).elements
  def iterator = elements // the "new" elements in 2.8

  case class SimpleVertex[T](value: T) extends Vertex[T]
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接