Mark Whitfield Mark Whitfield - 25 days ago 5
Scala Question

Scala -- multiple F-bounded types in inheritance tree

I have a generic F-bounded trait in Scala. Lets me write methods that return the same underlying implementation type, super! But now lets say a sub-trait also defines methods that require F-bounding. Scala is sending me back compile errors that don't make sense:

package sandbox

import sandbox.ComplexImpl.AnyComplexImpl

import scala.language.existentials

trait FBounded[IMPL <: FBounded[IMPL]] { self: IMPL =>
def foo: IMPL

trait FBoundedUser[F <: FBounded[F]] {
def bar(value: F): F =

trait SimpleImpl extends FBounded[SimpleImpl] {
override def foo: SimpleImpl = this

object SimpleUser extends FBoundedUser[SimpleImpl]

// A-OK so far...

trait ComplexImpl[IMPL <: ComplexImpl[IMPL]] extends FBounded[IMPL] { self: IMPL =>
def baz: IMPL

object ComplexImpl {
type AnyComplexImpl = ComplexImpl[T] forSome { type T <: ComplexImpl[T] }

object ComplexUser1 extends FBoundedUser[ComplexImpl[_]]
object ComplexUser2 extends FBoundedUser[AnyComplexImpl]

Trying to compile with either of
results in:

Error:(32, 29) type arguments [sandbox.ComplexImpl.AnyComplexImpl] do not conform to trait
FBoundedUser's type parameter bounds [F <: sandbox.FBounded[F]]

Which doesn't make sense to me.
definitely implements
. Am I missing something, or is the type checker just failing me here?


class Concrete() extends ComplexImpl[Concrete] {
override def baz: Concrete = this

override def foo: Concrete = this
object ComplexUser3 extends FBoundedUser[Concrete]

Compiles just fine. So why won't the generic version?


The constraint requires AnyComplexImpl to implement FBounded[AnyComplexImpl], which it doesn't, not just FBounded[T] forSome { type T <: ComplexImpl[T] }.

It works if you make FBounded and ComplexImpl covariant. The compiler reasons that:

  1. AnyComplexImpl is a supertype of any ComplexImpl[T], where T <: ComplexImpl[T];

  2. So FBounded[T] forSome { type T <: ComplexImpl[T] } is a subtype of FBounded[AnyComplexImpl];

  3. So AnyComplexImpl is a subtype of FBounded[AnyComplexImpl].

But I suspect trying to mix F-bounded types and existential is likely to lead to other problems down the line. The reason ComplexUser1 and ComplexUser2 don't compile is precisely that they are not generic. Consider using actually generic version instead:

def complexUser4[T <: ComplexImpl[T]] = new FBoundedUser[T] {}