I know it’s a necro, but I have fought with this problem extensively some time ago.
F-bounded polymorphysm is different from ‘self’ member type in the returned type.
If you have a type hierarchy and invoke a method returning ‘the same type’ for some abstract class a :A
, in F-bounded polymorphysm the result is A
. In bound type member approach, the result is a.Self
. This matters in type inference, especially for implicits.
Compare:
trait Base1[+Self <: FBound[Self]] {
def copy :Self
}
trait Sub1 extends Base1[Sub1]
class Impl1 extends Sub1 with Base1[Impl1] {
def copy = this
}
trait Base2 {
type Self <: Base
copy :Self
}
trait Sub2 extends Base2 { type Self <: Sub2 }
class Impl2 extends Sub2 {
type Self = Impl2
def copy = this
}
def copy1[T <: Base1[T]](x :T) = x.self
def copy2(x :Base2) = x.self
val proto1 = new Impl1 :Sub1
val proto2 = new Impl2 :Sub2
val a = copy1(proto1)
val b = copy2(proto2)
The type of a
is Sub1
, but the type of b
is proto2.Self <: Sub2
.
This can be an advantage of member types: method copy2
is simpler, and no matter
how specific an object you pass in, you’ll get a value of the exact same type.
It is however a huge pain when applied recursively:
class Ev1[T <: Base1[T]]
implicit val sub1Ev = new Ev1
class Ev2[T <: Base2]
val sub2Ev = new Ev2
class Foo
implicit def foo1[T <: Base1[T]](x :T)(implicit ev :Ev1[T]) = new Foo
implicit def foo2[T <: Base2](x :T)(implicit ev :Ev2[T]) = new Foo
def foo[T](x :T)(implicit ev:T => Foo) = ev(x)
foo1(proto1.self) //compiles
foo2(proto2.self) //does not compile
foo(proto1.self) //ok
foo(proto2.self) //implicit not found
The implicit case is a pain, because now always possibility to specify the type arguments manually. Depending on complexity of the actual case, it is often possible to do some tricks in order to guide the compiler, but it takes quite a bit of experience, time, and method signatures are much more complicated.