Question about type members vs. type arguments

24 views
Skip to first unread message

rklaehn

unread,
Oct 28, 2011, 5:54:39 AM10/28/11
to scala-user
Hi all,

I am using type members to implement "copy with changes" in a large
class hierarchy. The idea is that an abstract base type should know
which type it will eventually have so that it can return the proper
result in these kinds of methods. But I can't seem to get it to work.

In the code below, since I have overridden Self to HasZ, I would
expect the compiler to know that withX returns a HasZ, which also has
a withY method. But that does not seem to be the case. Instead I get
the confusing error message "found : HasZ.this.Self#Self, required:
HasZ.this.Self".

I can get it to work using type arguments instead of type members (see
below), but I would prefer the type members variant. And I would like
to understand the difference.

Here is the code that doesn't work:

package typemembers

trait Node {
type Self <: Node

def self : Self = this.asInstanceOf[Self]
}

trait HasX extends Node {
override type Self <: HasX

def withX(value: Int): Self = self
}

trait HasY extends Node {
override type Self <: HasY

def withY(value: Int): Self = self
}

trait HasZ extends Node with HasX with HasY {
override type Self <: HasZ

def withZ(value: String): Self = withX(1).withY(2)
}

And the error message:

error: type mismatch;
found : HasZ.this.Self#Self
required: HasZ.this.Self
def withZ(value: String): Self = withX(1).withY(2)

Here is the type arguments version that does work:

package typearguments

trait Node[Self <: Node[Self]] {

def self: Self = this.asInstanceOf[Self]
}

trait HasX[Self <: HasX[Self]] extends Node[Self] {
def withX(value: Int): Self = self
}

trait HasY[Self <: HasY[Self]] extends Node[Self] {
def withY(value: Int): Self = self
}

trait HasZ[Self <: HasZ[Self]] extends HasX[Self] with HasY[Self] {
def withZ(value: String) : Self = withX(1).withY(2)
}

Adriaan Moors

unread,
Oct 28, 2011, 6:23:44 AM10/28/11
to rklaehn, scala-user
trait Node {
 type Self <: Node

 def self : Self = this.asInstanceOf[Self]
}

trait HasX extends Node {
 override type Self <: HasX

 def withX(value: Int): Self = self
}

trait HasY extends Node {
 override type Self <: HasY

 def withY(value: Int): Self = self
}

trait HasZ extends Node with HasX with HasY {
 override type Self <: HasZ

 def withZ(value: String): Self = withX(1).withY(2)
}

And the error message:

error: type mismatch;
found   : HasZ.this.Self#Self
required: HasZ.this.Self
def withZ(value: String): Self = withX(1).withY(2)

let's consider step by step how withX(1).withY(2) is type checked:

with the following refactoring the error should become a bit clearer:

scala> trait HasZ extends Node with HasX with HasY {
     |  override type Self <: HasZ
     | 
     |  def withZ(value: String): Self = {
     |    val wx = withX(1)
     |    wx.withY(2)
     |  }
     | }
<console>:15: error: type mismatch;
 found   : wx.Self
 required: HasZ.this.Self
          wx.withY(2)
                  ^

wx and this are different values, so types that depend on them (wx.Self and this.Self) must be considered different types (there's no telling which concrete types you chose for them when creating instances)

to explain the actual error message you're getting:
since there is no such value wx in your code, the compiler has to invent one
it'll use an existential for that since it knows there has to be such a value with the given type, 
but it doesn't know exactly what it is, so we get:

withX(1).withY(2) : (wx.Self forSome {val wx: this.Self})  // this: HasZ

the latter type (desugared, it's actually wx.type#Self forSome {val wx: this.Self}) can be written more compactly as this.Self#Self

your type arguments version is more strict than the version with type arguments, as it enforces the Self to be the same type in the whole chain of method calls

here's the equivalent version using type members:

trait Node {
 type Self <: Node {type Self = Node.this.Self}

 def self : Self = this.asInstanceOf[Self]
}

trait HasX extends Node {
 type Self <: HasX {type Self = HasX.this.Self}

 def withX(value: Int): Self = self
}

trait HasY extends Node {
 type Self <: HasY {type Self = HasY.this.Self}

 def withY(value: Int): Self = self
}

trait HasZ extends Node with HasX with HasY {
 type Self <: HasZ {type Self = HasZ.this.Self}

rklaehn

unread,
Oct 28, 2011, 7:45:09 AM10/28/11
to scala-user
OK. Thanks a lot.

I was trying to avoid using a type argument to make using the traits
on concrete classes easier. Is using a type argument the preferred
approach for this kind of problem?

cheers,

Rüdiger
Reply all
Reply to author
Forward
0 new messages