9
votes

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
Ah yes. The "Scala has no MyType" problem again.Rex Kerr
as you can see I had a look at this in SO, and tried proposed variants. it works well for quite simple constructs (like c.incr().decr() example in Martin's paper), but with collections it doesn't.tuxSlayer
yeah. got the point why after reading your discussion here scala-lang.org/node/6649, thankstuxSlayer

2 Answers

5
votes

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

1
votes

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]).