Mixing type parameters and abstract types in scala

后端 未结 2 1776
野趣味
野趣味 2021-02-08 16:57

I am trying to use the answer of a preceding question to implement a small graph library. The idea is to consider graphs as colections, where vertices wrap collection elements.<

相关标签:
2条回答
  • 2021-02-08 17:39

    Compiling this with -explaintypes yields:

    <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
    

    Now, I was about to write I don't understand how T <: T can be false -- it is almost like T was defined twice, which, of course, is the whole problem. Here:

    abstract class GraphKind[T] { 
    
      type V <: Vertex[T] 
      type G <: Graph[T] 
    
      def newGraph(): G 
    
      abstract class Graph[T] extends Collection[T]{ 
    

    Ok, class GraphKind is parameterized with T and type G must be a Graph[T]. Now, class Graph is also parameterized, and its parameter is also called T. To prevent confusing, let's rewrite it:

      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]
      }
    

    Note that this is EXACTLY EQUAL to what you wrote. I'm just using a different name for the type parameter, so that it doesn't get confused with the T that is parameterizing GraphKind.

    So, here is the logic:

    G <: Graph[T]
    Graph[T2] <: Collection[T2]
    Graph[T2] <: G  // self type
    

    which implies that

    Graph[T2] <: Graph[T]
    

    And, because Graph extends Collection:

    Collection[T2] <: Collection[T]
    

    But there is no guarantee that this is true. I do not understand why the problem does not show up when the inheritance is not present. Fix:

    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 + ")"
      }
    }
    

    Since Vertex and Graph will be tied to one instance of GraphKind, then T will be fixed to whatever it was defined for that instance. For example:

    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")
                    ^
    
    0 讨论(0)
  • 2021-02-08 17:58

    It seems that Vertex's belonging to a particular graph and only that graph can be represented best in the type system with a nested Vertex trait in the Graph. Is what you are trying to achieve met with the following structure?

    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]
    }
    
    0 讨论(0)
提交回复
热议问题