Scala self type and this.type in collections issue

2019-04-06 17:49发布

I'm trying to wrap my head around abstract and explicit self types in scala. Lets consider this example: I want to create a base for extensible tree as simple as this:

trait Tree {
  def children: Iterable[Tree]
  def descendants: Iterable[Tree] = { val dv = children.view; dv ++ (dv.flatMap { _.children }) }
}

However, I want to be able to extend tree nodes with some methods and use these methods like: tree.children foreach { _.newMethod() }

For this I've tried:

A. this.type: FAIL

trait Tree {
    def children: Iterable[this.type] 
    def descendants: Iterable[this.type] = {
      val dv = children.view
      // FAIL: type mismatch;  found   :  scala.collection.IterableView[com.abovobo.data.Tree,Iterable[_]]  required: Iterable[Tree.this.type] 
      // dv ++ (dv.flatMap { _.children })
      // OK: 
      dv.++[this.type, Iterable[this.type]](dv.flatMap[this.type, Iterable[this.type]]{ _.children })
    }
}

Working variant are pretty clumsy.

B. Abstract types: FAIL

trait Tree {
    type Node <: Tree

    def children: Iterable[Node]  
    def descendants: Iterable[Node] = {
        val dv = children.view
        // FAIL: type mismatch;  found   : scala.collection.IterableView[com.abovobo.data.Tree#Node,Iterable[_]]  required: Iterable[Tree.this.Node] 
        dv ++ (dv.flatMap { _.children })
    }
}

Doesn't work at all due to path specific type mismatch as I understood.

C. Type params (generics): OK

trait Tree[+Node <: Tree[Node]] {

    def children: Iterable[Node]

    def descendants: Iterable[Node] = {
       val dv = children.view
       dv ++ (dv.flatMap { _.children })
    }
}

Works OK, but not so good to maintain in derived classes.

Any ideas how to make first two variants working without a tons of code?

Also, with this.type I've run into problems with implementation.

trait BiDTree extends Tree {
    def parent: Option[this.type]
}

// how to accept this param? Option[TreeImpl] doesn't work. 
class TreeImpl(val parent: Option[???]) extends BiDTree {
  // ...
}

Thanks!

2条回答
Explosion°爆炸
2楼-- · 2019-04-06 17:56

Without really understanding what the problem is you have with (C) you could try a variant of (B):

trait Tree {
    type Node <: Tree

    def children: Iterable[Tree#Node]  
    def descendants: Iterable[Tree#Node] = {
        val dv = children.view
        dv ++ (dv.flatMap { _.children })
    }
}

Which avoids your path specific type problem. By the way you should really have a look at http://www.assembla.com/spaces/scala-graph/wiki

查看更多
够拽才男人
3楼-- · 2019-04-06 18:00

At the end I've settled with what was proposed in this discussion http://www.scala-lang.org/node/6649:

trait Tree[+Node <: Tree[Node]] {
    this: Node =>

    def children: Iterable[Node]

    def descendants: Iterable[Node] = {
       val dv = children.view
       dv ++ (dv.flatMap { _.children })
    }
}  

I.e. variant (C) but with explicit self type. This gives a chance to use this in other methods (say, method find(path: String): Option[Node]).

查看更多
登录 后发表回答